diff --git a/Telegram-iOS/en.lproj/Localizable.strings b/Telegram-iOS/en.lproj/Localizable.strings
index b2d578e6f0..9828490834 100644
--- a/Telegram-iOS/en.lproj/Localizable.strings
+++ b/Telegram-iOS/en.lproj/Localizable.strings
@@ -4755,20 +4755,20 @@ Any member of this group will be able to see messages in the channel.";
"ChatSearch.ResultsTooltip" = "Tap to view as a list.";
-"Updated.JustNow" = "updated just now";
-"Updated.MinutesAgo_0" = "updated %@ minutes ago"; //three to ten
-"Updated.MinutesAgo_1" = "updated 1 minute ago"; //one
-"Updated.MinutesAgo_2" = "updated 2 minutes ago"; //two
-"Updated.MinutesAgo_3_10" = "updated %@ minutes ago"; //three to ten
-"Updated.MinutesAgo_many" = "updated %@ minutes ago"; // more than ten
-"Updated.MinutesAgo_any" = "updated %@ minutes ago"; // more than ten
-"Updated.HoursAgo_0" = "updated %@ hours ago";
-"Updated.HoursAgo_1" = "updated 1 hour ago";
-"Updated.HoursAgo_2" = "updated 2 hours ago";
-"Updated.HoursAgo_3_10" = "updated %@ hours ago";
-"Updated.HoursAgo_any" = "updated %@ hours ago";
-"Updated.HoursAgo_many" = "updated %@ hours ago";
-"Updated.HoursAgo_0" = "updated %@ hours ago";
-"Updated.YesterdayAt" = "updated yesterday at %@";
-"Updated.AtDate" = "updated %@";
-"Updated.TodayAt" = "updated today at %@";
\ No newline at end of file
+"Updated.JustNow" = "just now";
+"Updated.MinutesAgo_0" = "%@ minutes ago"; //three to ten
+"Updated.MinutesAgo_1" = "1 minute ago"; //one
+"Updated.MinutesAgo_2" = "2 minutes ago"; //two
+"Updated.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten
+"Updated.MinutesAgo_many" = "%@ minutes ago"; // more than ten
+"Updated.MinutesAgo_any" = "%@ minutes ago"; // more than ten
+"Updated.HoursAgo_0" = "%@ hours ago";
+"Updated.HoursAgo_1" = "1 hour ago";
+"Updated.HoursAgo_2" = "2 hours ago";
+"Updated.HoursAgo_3_10" = "%@ hours ago";
+"Updated.HoursAgo_any" = "%@ hours ago";
+"Updated.HoursAgo_many" = "%@ hours ago";
+"Updated.HoursAgo_0" = "%@ hours ago";
+"Updated.YesterdayAt" = "yesterday at %@";
+"Updated.AtDate" = "%@";
+"Updated.TodayAt" = "today at %@";
diff --git a/Telegram_Buck.xcworkspace/xcshareddata/xcschemes/Telegram_Buck+IntentsExtension.xcscheme b/Telegram_Buck.xcworkspace/xcshareddata/xcschemes/Telegram_Buck+IntentsExtension.xcscheme
index ce9b325629..a48bce0372 100644
--- a/Telegram_Buck.xcworkspace/xcshareddata/xcschemes/Telegram_Buck+IntentsExtension.xcscheme
+++ b/Telegram_Buck.xcworkspace/xcshareddata/xcschemes/Telegram_Buck+IntentsExtension.xcscheme
@@ -44,6 +44,8 @@
shouldUseLaunchSchemeArgsEnv = "YES">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
deliverOnMainQueue).start(completed: {
f(.default)
diff --git a/submodules/Display/Display/DisplayLinkAnimator.swift b/submodules/Display/Display/DisplayLinkAnimator.swift
index a7cbd79858..acd8db711f 100644
--- a/submodules/Display/Display/DisplayLinkAnimator.swift
+++ b/submodules/Display/Display/DisplayLinkAnimator.swift
@@ -65,3 +65,45 @@ public final class DisplayLinkAnimator {
}
}
}
+
+public final class ConstantDisplayLinkAnimator {
+ private var displayLink: CADisplayLink!
+ private let update: () -> Void
+ private var completed = false
+
+ public var isPaused: Bool = true {
+ didSet {
+ if self.isPaused != oldValue {
+ self.displayLink.isPaused = self.isPaused
+ }
+ }
+ }
+
+ public init(update: @escaping () -> Void) {
+ self.update = update
+
+ self.displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in
+ self?.tick()
+ }), selector: #selector(DisplayLinkTarget.event))
+ self.displayLink.isPaused = true
+ self.displayLink.add(to: RunLoop.main, forMode: .common)
+ }
+
+ deinit {
+ self.displayLink.isPaused = true
+ self.displayLink.invalidate()
+ }
+
+ public func invalidate() {
+ self.displayLink.isPaused = true
+ self.displayLink.invalidate()
+ }
+
+ @objc private func tick() {
+ if self.completed {
+ return
+ }
+ self.update()
+ }
+}
+
diff --git a/submodules/Display/Display/KeyboardManager.swift b/submodules/Display/Display/KeyboardManager.swift
index 56769ae4b9..3cea17e8f1 100644
--- a/submodules/Display/Display/KeyboardManager.swift
+++ b/submodules/Display/Display/KeyboardManager.swift
@@ -144,3 +144,23 @@ class KeyboardManager {
self.previousFirstResponderView = firstResponderView
}
}
+
+final class KeyboardViewManager {
+ private let host: StatusBarHost
+
+ init(host: StatusBarHost) {
+ self.host = host
+ }
+
+ func update(leftEdge: CGFloat, transition: ContainedViewLayoutTransition) {
+ guard let keyboardWindow = self.host.keyboardWindow else {
+ return
+ }
+ let previousPosition = keyboardWindow.layer.position
+ let updatedPosition = CGPoint(x: leftEdge + keyboardWindow.layer.bounds.width / 2.0, y: previousPosition.y)
+ keyboardWindow.layer.position = updatedPosition
+ if transition.isAnimated && !previousPosition.x.isEqual(to: updatedPosition.x) {
+ transition.animatePositionAdditive(layer: keyboardWindow.layer, offset: CGPoint(x: previousPosition.x - updatedPosition.x, y: 0.0))
+ }
+ }
+}
diff --git a/submodules/Display/Display/Navigation/NavigationContainer.swift b/submodules/Display/Display/Navigation/NavigationContainer.swift
index add58d141f..0321302ae4 100644
--- a/submodules/Display/Display/Navigation/NavigationContainer.swift
+++ b/submodules/Display/Display/Navigation/NavigationContainer.swift
@@ -54,10 +54,12 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
private final class TopTransition {
+ let type: PendingChild.TransitionType
let previous: Child
let coordinator: NavigationTransitionCoordinator
- init(previous: Child, coordinator: NavigationTransitionCoordinator) {
+ init(type: PendingChild.TransitionType, previous: Child, coordinator: NavigationTransitionCoordinator) {
+ self.type = type
self.previous = previous
self.coordinator = coordinator
}
@@ -77,10 +79,10 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
private(set) var isReady: Bool = false
var isReadyUpdated: (() -> Void)?
var controllerRemoved: (ViewController) -> Void
- var keyboardManager: KeyboardManager? {
+ var keyboardViewManager: KeyboardViewManager? {
didSet {
- if self.keyboardManager !== oldValue {
- self.keyboardManager?.surfaces = self.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
+ if self.keyboardViewManager !== oldValue {
+
}
}
}
@@ -139,14 +141,17 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
topController.viewWillDisappear(true)
- let topView = topController.view!
+ let topNode = topController.displayNode
bottomController.containerLayoutUpdated(layout, transition: .immediate)
bottomController.viewWillAppear(true)
- let bottomView = bottomController.view!
+ let bottomNode = bottomController.displayNode
- let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: topController.navigationBar, bottomView: bottomView, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self] progress, transition in
+ let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self, topNode: topNode, topNavigationBar: topController.navigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self] progress, transition, topFrame, bottomFrame in
if let strongSelf = self {
- strongSelf.keyboardManager?.surfaces = strongSelf.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
+ if let top = strongSelf.state.top {
+ strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition)
+ }
+ //strongSelf.keyboardManager?.surfaces = strongSelf.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
/*for i in 0 ..< strongSelf._viewControllers.count {
if let controller = strongSelf._viewControllers[i].controller as? ViewController {
if i < strongSelf._viewControllers.count - 1 {
@@ -159,13 +164,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
})
bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true)
- self.state.transition = TopTransition(previous: Child(value: bottomController, layout: layout), coordinator: navigationTransitionCoordinator)
+ self.state.transition = TopTransition(type: .pop, previous: Child(value: bottomController, layout: layout), coordinator: navigationTransitionCoordinator)
}
case .changed:
if let navigationTransitionCoordinator = self.state.transition?.coordinator, !navigationTransitionCoordinator.animatingCompletion {
let translation = recognizer.translation(in: self.view).x
let progress = max(0.0, min(1.0, translation / self.view.frame.width))
- navigationTransitionCoordinator.progress = progress
+ navigationTransitionCoordinator.updateProgress(progress, transition: .immediate, completion: {})
}
case .ended, .cancelled:
if let navigationTransitionCoordinator = self.state.transition?.coordinator, !navigationTransitionCoordinator.animatingCompletion {
@@ -274,7 +279,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
if pending.isReady {
self.state.pending = nil
let previous = self.state.top
- previous?.value.view.endEditing(true)
+ //previous?.value.view.endEditing(true)
self.state.top = pending.value
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: layout.withUpdatedInputHeight(nil), transition: pending.transition)
statusBarTransition = pending.transition
@@ -293,7 +298,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
var updatedStatusBarStyle = self.statusBarStyle
if let top = self.state.top {
- self.applyLayout(layout: layout, to: top, transition: transition)
+ self.applyLayout(layout: layout, to: top, isMaster: true, transition: transition)
updatedStatusBarStyle = top.value.statusBar.statusBarStyle
} else {
updatedStatusBarStyle = .Ignore
@@ -304,13 +309,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
if self.state.transition == nil {
- self.keyboardManager?.surfaces = self.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
+ //self.keyboardManager?.surfaces = self.state.top?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
}
}
private func topTransition(from fromValue: Child?, to toValue: Child?, transitionType: PendingChild.TransitionType, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
if case .animated = transition, let fromValue = fromValue, let toValue = toValue {
- self.keyboardManager?.surfaces = fromValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
+ //self.keyboardManager?.surfaces = fromValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
if let currentTransition = self.state.transition {
assertionFailure()
}
@@ -338,7 +343,17 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
}
toValue.value.setIgnoreAppearanceMethodInvocations(false)
- let topTransition = TopTransition(previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, container: self.view, topView: topController.view, topNavigationBar: topController.navigationBar, bottomView: bottomController.view, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: nil))
+ let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, container: self, topNode: topController.displayNode, topNavigationBar: topController.navigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.navigationBar, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in
+ guard let strongSelf = self else {
+ return
+ }
+ switch transitionType {
+ case .push:
+ strongSelf.syncKeyboard(leftEdge: topFrame.minX - bottomFrame.width, transition: transition)
+ case .pop:
+ strongSelf.syncKeyboard(leftEdge: topFrame.minX, transition: transition)
+ }
+ }))
self.state.transition = topTransition
topTransition.coordinator.animateCompletion(0.0, completion: { [weak self, weak topTransition] in
@@ -353,13 +368,13 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
topTransition.previous.value.viewDidDisappear(true)
if let toValue = strongSelf.state.top, let layout = strongSelf.state.layout {
toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size)
- strongSelf.applyLayout(layout: layout, to: toValue, transition: .immediate)
+ strongSelf.applyLayout(layout: layout, to: toValue, isMaster: true, transition: .immediate)
toValue.value.viewDidAppear(true)
- strongSelf.keyboardManager?.surfaces = toValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
+ //strongSelf.keyboardManager?.surfaces = toValue.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
}
})
} else {
- self.keyboardManager?.surfaces = toValue?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
+ //self.keyboardManager?.surfaces = toValue?.value.view.flatMap({ [KeyboardSurface(host: $0)] }) ?? []
if let fromValue = fromValue {
fromValue.value.viewWillDisappear(false)
fromValue.value.setIgnoreAppearanceMethodInvocations(true)
@@ -368,7 +383,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
fromValue.value.viewDidDisappear(false)
}
if let toValue = toValue {
- self.applyLayout(layout: layout, to: toValue, transition: .immediate)
+ self.applyLayout(layout: layout, to: toValue, isMaster: true, transition: .immediate)
toValue.value.displayNode.frame = CGRect(origin: CGPoint(), size: layout.size)
toValue.value.viewWillAppear(false)
toValue.value.setIgnoreAppearanceMethodInvocations(true)
@@ -384,17 +399,34 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
return Child(value: value, layout: layout)
}
- private func applyLayout(layout: ContainerViewLayout, to child: Child, transition: ContainedViewLayoutTransition) {
- let childFrame = CGRect(origin: CGPoint(), size: layout.size)
+ private func applyLayout(layout: ContainerViewLayout, to child: Child, isMaster: Bool, transition: ContainedViewLayoutTransition) {
+ var childFrame = CGRect(origin: CGPoint(), size: layout.size)
+ var shouldSyncKeyboard = false
+ if let transition = self.state.transition {
+ childFrame.origin.x = child.value.displayNode.frame.origin.x
+ switch transition.type {
+ case .pop:
+ shouldSyncKeyboard = true
+ case .push:
+ break
+ }
+ }
if child.value.displayNode.frame != childFrame {
transition.updateFrame(node: child.value.displayNode, frame: childFrame)
}
+ if shouldSyncKeyboard && isMaster {
+ self.syncKeyboard(leftEdge: childFrame.minX, transition: transition)
+ }
if child.layout != layout {
child.layout = layout
child.value.containerLayoutUpdated(layout, transition: transition)
}
}
+ private func syncKeyboard(leftEdge: CGFloat, transition: ContainedViewLayoutTransition) {
+ self.keyboardViewManager?.update(leftEdge: leftEdge, transition: transition)
+ }
+
private func pendingChildIsReady(_ child: PendingChild) {
if let pending = self.state.pending, pending === child {
pending.isReady = true
diff --git a/submodules/Display/Display/Navigation/NavigationController.swift b/submodules/Display/Display/Navigation/NavigationController.swift
index a206e983e6..384179b408 100644
--- a/submodules/Display/Display/Navigation/NavigationController.swift
+++ b/submodules/Display/Display/Navigation/NavigationController.swift
@@ -151,7 +151,7 @@ open class NavigationController: UINavigationController, ContainableController,
}
var statusBarHost: StatusBarHost?
- var keyboardManager: KeyboardManager?
+ var keyboardViewManager: KeyboardViewManager?
public func updateMasterDetailsBlackout(_ blackout: MasterDetailLayoutBlackout?, transition: ContainedViewLayoutTransition) {
self.masterDetailsBlackout = blackout
@@ -341,12 +341,12 @@ open class NavigationController: UINavigationController, ContainableController,
if previousModalContainer == nil {
topModalDismissProgress = modalContainer.dismissProgress
if case .compact = layout.metrics.widthClass {
- modalContainer.container.keyboardManager = self.keyboardManager
+ modalContainer.container.keyboardViewManager = self.keyboardViewManager
} else {
- modalContainer.container.keyboardManager = nil
+ modalContainer.container.keyboardViewManager = nil
}
} else {
- modalContainer.container.keyboardManager = nil
+ modalContainer.container.keyboardViewManager = nil
}
previousModalContainer = modalContainer
}
@@ -358,9 +358,9 @@ open class NavigationController: UINavigationController, ContainableController,
switch rootContainer {
case let .flat(flatContainer):
if previousModalContainer == nil {
- flatContainer.keyboardManager = self.keyboardManager
+ flatContainer.keyboardViewManager = self.keyboardViewManager
} else {
- flatContainer.keyboardManager = nil
+ flatContainer.keyboardViewManager = nil
}
transition.updateFrame(node: flatContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: transition)
diff --git a/submodules/Display/Display/Navigation/NavigationModalContainer.swift b/submodules/Display/Display/Navigation/NavigationModalContainer.swift
index ad53c8c39e..0d01ad0723 100644
--- a/submodules/Display/Display/Navigation/NavigationModalContainer.swift
+++ b/submodules/Display/Display/Navigation/NavigationModalContainer.swift
@@ -53,6 +53,8 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate {
override func didLoad() {
super.didLoad()
+ self.view.disablesInteractiveKeyboardGestureRecognizer = true
+
self.scrollNode.view.alwaysBounceVertical = false
self.scrollNode.view.alwaysBounceHorizontal = false
self.scrollNode.view.bounces = false
diff --git a/submodules/Display/Display/NavigationTransitionCoordinator.swift b/submodules/Display/Display/NavigationTransitionCoordinator.swift
index 7ebe56c0fa..5cbf82a864 100644
--- a/submodules/Display/Display/NavigationTransitionCoordinator.swift
+++ b/submodules/Display/Display/NavigationTransitionCoordinator.swift
@@ -20,49 +20,41 @@ class NavigationTransitionCoordinator {
get {
return self._progress
}
- set(value) {
- self._progress = value
- self.updateProgress(transition: .immediate)
- }
}
- private let container: UIView
+ private let container: ASDisplayNode
private let transition: NavigationTransition
- let topView: UIView
- private let viewSuperview: UIView?
- let bottomView: UIView
+ let topNode: ASDisplayNode
+ let bottomNode: ASDisplayNode
private let topNavigationBar: NavigationBar?
private let bottomNavigationBar: NavigationBar?
- private let dimView: UIView
- private let shadowView: UIImageView
+ private let dimNode: ASDisplayNode
+ private let shadowNode: ASImageNode
private let inlineNavigationBarTransition: Bool
private(set) var animatingCompletion = false
private var currentCompletion: (() -> Void)?
- private var didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
+ private var didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)?
- init(transition: NavigationTransition, container: UIView, topView: UIView, topNavigationBar: NavigationBar?, bottomView: UIView, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition) -> Void)? = nil) {
+ init(transition: NavigationTransition, container: ASDisplayNode, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) {
self.transition = transition
self.container = container
self.didUpdateProgress = didUpdateProgress
- self.topView = topView
- switch transition {
- case .Push:
- self.viewSuperview = bottomView.superview
- case .Pop:
- self.viewSuperview = topView.superview
- }
- self.bottomView = bottomView
+ self.topNode = topNode
+ self.bottomNode = bottomNode
self.topNavigationBar = topNavigationBar
self.bottomNavigationBar = bottomNavigationBar
- self.dimView = UIView()
- self.dimView.backgroundColor = UIColor.black
- self.shadowView = UIImageView(image: shadowImage)
+ self.dimNode = ASDisplayNode()
+ self.dimNode.backgroundColor = UIColor.black
+ self.shadowNode = ASImageNode()
+ self.shadowNode.displaysAsynchronously = false
+ self.shadowNode.displayWithoutProcessing = true
+ self.shadowNode.image = shadowImage
if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden, topNavigationBar.canTransitionInline, bottomNavigationBar.canTransitionInline, topNavigationBar.item?.leftBarButtonItem == nil {
- var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container)
- var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container)
+ var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container.view)
+ var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container.view)
topFrame.origin.x = 0.0
bottomFrame.origin.x = 0.0
self.inlineNavigationBarTransition = true// topFrame.equalTo(bottomFrame)
@@ -72,23 +64,25 @@ class NavigationTransitionCoordinator {
switch transition {
case .Push:
- self.viewSuperview?.insertSubview(topView, belowSubview: topView)
+ self.container.insertSubnode(topNode, belowSubnode: topNode)
case .Pop:
- self.viewSuperview?.insertSubview(bottomView, belowSubview: topView)
+ self.container.insertSubnode(bottomNode, belowSubnode: topNode)
}
- self.viewSuperview?.insertSubview(self.dimView, belowSubview: topView)
- self.viewSuperview?.insertSubview(self.shadowView, belowSubview: dimView)
+ self.container.insertSubnode(self.dimNode, belowSubnode: topNode)
+ self.container.insertSubnode(self.shadowNode, belowSubnode: dimNode)
self.maybeCreateNavigationBarTransition()
- self.updateProgress(transition: .immediate)
+ self.updateProgress(0.0, transition: .immediate, completion: {})
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
- func updateProgress(transition: ContainedViewLayoutTransition) {
+ func updateProgress(_ progress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
+ self._progress = progress
+
let position: CGFloat
switch self.transition {
case .Push:
@@ -104,19 +98,25 @@ class NavigationTransitionCoordinator {
let containerSize = self.container.bounds.size
- self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * containerSize.width), y: 0.0), size: containerSize)
- self.dimView.frame = CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, self.topView.frame.minX), height: self.container.bounds.size.height - dimInset))
- self.shadowView.frame = CGRect(origin: CGPoint(x: self.dimView.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset))
- self.dimView.alpha = (1.0 - position) * 0.15
- self.shadowView.alpha = (1.0 - position) * 0.9
- self.bottomView.frame = CGRect(origin: CGPoint(x: ((position - 1.0) * containerSize.width * 0.3), y: 0.0), size: containerSize)
+ let topFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * containerSize.width), y: 0.0), size: containerSize)
+ let bottomFrame = CGRect(origin: CGPoint(x: ((position - 1.0) * containerSize.width * 0.3), y: 0.0), size: containerSize)
- self.updateNavigationBarTransition()
+ transition.updateFrame(node: self.topNode, frame: topFrame, completion: { _ in
+ completion()
+ })
+ transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX), height: self.container.bounds.size.height - dimInset)))
+ transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: self.dimNode.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset)))
+ transition.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15)
+ transition.updateAlpha(node: self.shadowNode, alpha: (1.0 - position) * 0.9)
- self.didUpdateProgress?(self.progress, transition)
+ transition.updateFrame(node: self.bottomNode, frame: bottomFrame)
+
+ self.updateNavigationBarTransition(transition: transition)
+
+ self.didUpdateProgress?(self.progress, transition, topFrame, bottomFrame)
}
- func updateNavigationBarTransition() {
+ func updateNavigationBarTransition(transition: ContainedViewLayoutTransition) {
if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition {
let position: CGFloat
switch self.transition {
@@ -126,8 +126,10 @@ class NavigationTransitionCoordinator {
position = progress
}
- topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position)
- bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position)
+ transition.animateView {
+ topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position)
+ bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position)
+ }
}
}
@@ -156,45 +158,36 @@ class NavigationTransitionCoordinator {
func animateCancel(_ completion: @escaping () -> ()) {
self.currentCompletion = completion
- UIView.animate(withDuration: 0.1, delay: 0.0, options: UIView.AnimationOptions(), animations: { () -> Void in
- self.progress = 0.0
- }) { (completed) -> Void in
- switch self.transition {
- case .Push:
- if let viewSuperview = self.viewSuperview {
- viewSuperview.addSubview(self.bottomView)
- } else {
- self.bottomView.removeFromSuperview()
- }
- self.topView.removeFromSuperview()
- case .Pop:
- if let viewSuperview = self.viewSuperview {
- viewSuperview.addSubview(self.topView)
- } else {
- self.topView.removeFromSuperview()
- }
- self.bottomView.removeFromSuperview()
+ self.updateProgress(0.0, transition: .animated(duration: 0.1, curve: .easeInOut), completion: { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+ switch strongSelf.transition {
+ case .Push:
+ strongSelf.topNode.removeFromSupernode()
+ case .Pop:
+ strongSelf.bottomNode.removeFromSupernode()
}
- self.dimView.removeFromSuperview()
- self.shadowView.removeFromSuperview()
+ strongSelf.dimNode.removeFromSupernode()
+ strongSelf.shadowNode.removeFromSupernode()
- self.endNavigationBarTransition()
+ strongSelf.endNavigationBarTransition()
- if let currentCompletion = self.currentCompletion {
- self.currentCompletion = nil
+ if let currentCompletion = strongSelf.currentCompletion {
+ strongSelf.currentCompletion = nil
currentCompletion()
}
- }
+ })
}
func complete() {
self.animatingCompletion = true
- self.progress = 1.0
+ self._progress = 1.0
- self.dimView.removeFromSuperview()
- self.shadowView.removeFromSuperview()
+ self.dimNode.removeFromSupernode()
+ self.shadowNode.removeFromSupernode()
self.endNavigationBarTransition()
@@ -224,8 +217,8 @@ class NavigationTransitionCoordinator {
}
}*/
- self.dimView.removeFromSuperview()
- self.shadowView.removeFromSuperview()
+ self.dimNode.removeFromSupernode()
+ self.shadowNode.removeFromSupernode()
self.endNavigationBarTransition()
@@ -236,17 +229,13 @@ class NavigationTransitionCoordinator {
}
if abs(velocity) < CGFloat.ulpOfOne && abs(self.progress) < CGFloat.ulpOfOne {
- UIView.animate(withDuration: 0.5, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: {
- self.progress = 1.0
- }, completion: { _ in
+ self.updateProgress(1.0, transition: .animated(duration: 0.5, curve: .spring), completion: {
f()
})
} else {
- UIView.animate(withDuration: Double(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options:UIView.AnimationOptions(), animations: { () -> Void in
- self.progress = 1.0
- }) { (completed) -> Void in
+ self.updateProgress(1.0, transition: .animated(duration: Double(max(0.05, min(0.2, abs(distance / velocity)))), curve: .easeInOut), completion: {
f()
- }
+ })
}
}
}
diff --git a/submodules/Display/Display/Spring.swift b/submodules/Display/Display/Spring.swift
index 169ed107db..ce4a55adf8 100644
--- a/submodules/Display/Display/Spring.swift
+++ b/submodules/Display/Display/Spring.swift
@@ -57,7 +57,7 @@ private func getTForX(_ x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat {
return t
}
-func bezierPoint(_ x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat
+public func bezierPoint(_ x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat
{
var value = calcBezier(getTForX(x, x1, x2), y1, y2)
if value >= 0.997 {
diff --git a/submodules/Display/Display/UIViewController+Navigation.h b/submodules/Display/Display/UIViewController+Navigation.h
index 9c3f5dc007..ed78c4f164 100644
--- a/submodules/Display/Display/UIViewController+Navigation.h
+++ b/submodules/Display/Display/UIViewController+Navigation.h
@@ -21,6 +21,7 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
@interface UIView (Navigation)
@property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer;
+@property (nonatomic) bool disablesInteractiveKeyboardGestureRecognizer;
@property (nonatomic, copy) bool (^ disablesInteractiveTransitionGestureRecognizerNow)();
@property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling;
diff --git a/submodules/Display/Display/UIViewController+Navigation.m b/submodules/Display/Display/UIViewController+Navigation.m
index d08b6a9780..09a327f821 100644
--- a/submodules/Display/Display/UIViewController+Navigation.m
+++ b/submodules/Display/Display/UIViewController+Navigation.m
@@ -35,6 +35,7 @@ static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNa
static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey;
static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey;
static const void *disablesInteractiveTransitionGestureRecognizerKey = &disablesInteractiveTransitionGestureRecognizerKey;
+static const void *disablesInteractiveKeyboardGestureRecognizerKey = &disablesInteractiveKeyboardGestureRecognizerKey;
static const void *disablesInteractiveTransitionGestureRecognizerNowKey = &disablesInteractiveTransitionGestureRecognizerNowKey;
static const void *disableAutomaticKeyboardHandlingKey = &disableAutomaticKeyboardHandlingKey;
static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey;
@@ -233,6 +234,14 @@ static bool notyfyingShiftState = false;
[self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey];
}
+- (bool)disablesInteractiveKeyboardGestureRecognizer {
+ return [[self associatedObjectForKey:disablesInteractiveKeyboardGestureRecognizerKey] boolValue];
+}
+
+- (void)setDisablesInteractiveKeyboardGestureRecognizer:(bool)disablesInteractiveKeyboardGestureRecognizer {
+ [self setAssociatedObject:@(disablesInteractiveKeyboardGestureRecognizer) forKey:disablesInteractiveKeyboardGestureRecognizerKey];
+}
+
- (bool (^)())disablesInteractiveTransitionGestureRecognizerNow {
return [self associatedObjectForKey:disablesInteractiveTransitionGestureRecognizerNowKey];
}
diff --git a/submodules/Display/Display/WindowContent.swift b/submodules/Display/Display/WindowContent.swift
index b592479516..8a2562fd69 100644
--- a/submodules/Display/Display/WindowContent.swift
+++ b/submodules/Display/Display/WindowContent.swift
@@ -129,6 +129,9 @@ public func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UI
if view.disablesInteractiveTransitionGestureRecognizer {
return true
}
+ if view.disablesInteractiveKeyboardGestureRecognizer {
+ return true
+ }
if let f = view.disablesInteractiveTransitionGestureRecognizerNow, f() {
return true
}
@@ -279,6 +282,7 @@ public class Window1 {
private let statusBarHost: StatusBarHost?
private let statusBarManager: StatusBarManager?
private let keyboardManager: KeyboardManager?
+ private let keyboardViewManager: KeyboardViewManager?
private var statusBarChangeObserver: AnyObject?
private var keyboardRotationChangeObserver: AnyObject?
private var keyboardFrameChangeObserver: AnyObject?
@@ -344,10 +348,12 @@ public class Window1 {
statusBarHeight = statusBarHost.statusBarFrame.size.height
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.keyboardManager = nil
+ self.keyboardViewManager = nil
}
let isLandscape = boundsSize.width > boundsSize.height
@@ -692,7 +698,7 @@ public class Window1 {
if let rootController = self._rootController {
if let rootController = rootController as? NavigationController {
rootController.statusBarHost = self.statusBarHost
- rootController.keyboardManager = self.keyboardManager
+ rootController.keyboardViewManager = self.keyboardViewManager
}
if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero {
rootController.displayNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size)
diff --git a/submodules/MtProtoKit/TON/TON.h b/submodules/MtProtoKit/TON/TON.h
index 18d6b5585a..79084ea377 100644
--- a/submodules/MtProtoKit/TON/TON.h
+++ b/submodules/MtProtoKit/TON/TON.h
@@ -78,16 +78,12 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest;
- (MTSignal *)createKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword;
-- (MTSignal *)getTestWalletAccountAddressWithPublicKey:(NSString *)publicKey;
-- (MTSignal *)getTestGiverAccountState;
-- (MTSignal *)getTestGiverAddress;
-- (MTSignal *)testGiverSendGramsWithAccountState:(TONAccountState *)accountState accountAddress:(NSString *)accountAddress amount:(int64_t)amount;
+- (MTSignal *)getWalletAccountAddressWithPublicKey:(NSString *)publicKey;
- (MTSignal *)getAccountStateWithAddress:(NSString *)accountAddress;
- (MTSignal *)sendGramsFromKey:(TONKey *)key localPassword:(NSData *)localPassword fromAddress:(NSString *)fromAddress toAddress:(NSString *)address amount:(int64_t)amount textMessage:(NSString *)textMessage;
- (MTSignal *)exportKey:(TONKey *)key localPassword:(NSData *)localPassword;
- (MTSignal *)importKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword wordList:(NSArray *)wordList;
-- (MTSignal *)deleteKeyWithPublicKey:(NSString *)publicKey;
-- (MTSignal *)makeWalletInitialized:(TONKey *)key localPassword:(NSData *)localPassword;
+- (MTSignal *)deleteKey:(TONKey *)key;
- (MTSignal *)getTransactionListWithAddress:(NSString * _Nonnull)address lt:(int64_t)lt hash:(NSData * _Nonnull)hash;
@end
diff --git a/submodules/MtProtoKit/TON/TON.mm b/submodules/MtProtoKit/TON/TON.mm
index 35f14ea149..6c483c6c9e 100644
--- a/submodules/MtProtoKit/TON/TON.mm
+++ b/submodules/MtProtoKit/TON/TON.mm
@@ -353,11 +353,11 @@ typedef enum {
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
}
-- (MTSignal *)getTestWalletAccountAddressWithPublicKey:(NSString *)publicKey {
+- (MTSignal *)getWalletAccountAddressWithPublicKey:(NSString *)publicKey {
return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
NSData *publicKeyData = [publicKey dataUsingEncoding:NSUTF8StringEncoding];
if (publicKeyData == nil) {
- [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in getTestWalletAccountAddressWithPublicKey"]];
+ [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in getWalletAccountAddressWithPublicKey"]];
return [[MTBlockDisposable alloc] initWithBlock:^{}];
}
@@ -377,8 +377,8 @@ typedef enum {
}
}];
- auto query = make_object(
- make_object(
+ auto query = make_object(
+ make_object(
makeString(publicKeyData)
)
);
@@ -389,89 +389,6 @@ typedef enum {
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
}
-- (MTSignal *)getTestGiverAccountState {
- return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
- uint64_t requestId = _nextRequestId;
- _nextRequestId += 1;
-
- _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) {
- if (object->get_id() == tonlib_api::error::ID) {
- auto error = tonlib_api::move_object_as(object);
- [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
- } else if (object->get_id() == tonlib_api::testGiver_accountState::ID) {
- auto result = tonlib_api::move_object_as(object);
- TONTransactionId *lastTransactionId = nil;
- if (result->last_transaction_id_ != nullptr) {
- lastTransactionId = [[TONTransactionId alloc] initWithLt:result->last_transaction_id_->lt_ transactionHash:makeData(result->last_transaction_id_->hash_)];
- }
- [subscriber putNext:[[TONAccountState alloc] initWithIsInitialized:true balance:result->balance_ seqno:result->seqno_ lastTransactionId:lastTransactionId syncUtime:result->sync_utime_]];
- [subscriber putCompletion];
- } else {
- assert(false);
- }
- }];
-
- auto query = make_object();
- _client->send({ requestId, std::move(query) });
-
- return [[MTBlockDisposable alloc] initWithBlock:^{
- }];
- }] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
-}
-
-- (MTSignal *)getTestGiverAddress {
- return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
- uint64_t requestId = _nextRequestId;
- _nextRequestId += 1;
-
- _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) {
- if (object->get_id() == tonlib_api::error::ID) {
- auto error = tonlib_api::move_object_as(object);
- [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
- } else if (object->get_id() == tonlib_api::accountAddress::ID) {
- auto result = tonlib_api::move_object_as(object);
- [subscriber putNext:[[NSString alloc] initWithUTF8String:result->account_address_.c_str()]];
- [subscriber putCompletion];
- } else {
- assert(false);
- }
- }];
-
- auto query = make_object();
- _client->send({ requestId, std::move(query) });
-
- return [[MTBlockDisposable alloc] initWithBlock:^{
- }];
- }] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
-}
-
-- (MTSignal *)testGiverSendGramsWithAccountState:(TONAccountState *)accountState accountAddress:(NSString *)accountAddress amount:(int64_t)amount {
- return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
- uint64_t requestId = _nextRequestId;
- _nextRequestId += 1;
-
- _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) {
- if (object->get_id() == tonlib_api::error::ID) {
- auto error = tonlib_api::move_object_as(object);
- [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
- } else {
- [subscriber putCompletion];
- }
- }];
-
- auto query = make_object(make_object(
- accountAddress.UTF8String),
- accountState.seqno,
- amount,
- std::string()
- );
- _client->send({ requestId, std::move(query) });
-
- return [[MTBlockDisposable alloc] initWithBlock:^{
- }];
- }] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
-}
-
- (MTSignal *)getAccountStateWithAddress:(NSString *)accountAddress {
return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
uint64_t requestId = _nextRequestId;
@@ -489,16 +406,8 @@ typedef enum {
}
[subscriber putNext:[[TONAccountState alloc] initWithIsInitialized:false balance:result->account_state_->balance_ seqno:-1 lastTransactionId:lastTransactionId syncUtime:result->account_state_->sync_utime_]];
[subscriber putCompletion];
- } else if (object->get_id() == tonlib_api::generic_accountStateTestWallet::ID) {
- auto result = tonlib_api::move_object_as(object);
- TONTransactionId *lastTransactionId = nil;
- if (result->account_state_->last_transaction_id_ != nullptr) {
- lastTransactionId = [[TONTransactionId alloc] initWithLt:result->account_state_->last_transaction_id_->lt_ transactionHash:makeData(result->account_state_->last_transaction_id_->hash_)];
- }
- [subscriber putNext:[[TONAccountState alloc] initWithIsInitialized:true balance:result->account_state_->balance_ seqno:result->account_state_->seqno_ lastTransactionId:lastTransactionId syncUtime:result->account_state_->sync_utime_]];
- [subscriber putCompletion];
- } else if (object->get_id() == tonlib_api::generic_accountStateTestGiver::ID) {
- auto result = tonlib_api::move_object_as(object);
+ } else if (object->get_id() == tonlib_api::generic_accountStateWallet::ID) {
+ auto result = tonlib_api::move_object_as(object);
TONTransactionId *lastTransactionId = nil;
if (result->account_state_->last_transaction_id_ != nullptr) {
lastTransactionId = [[TONTransactionId alloc] initWithLt:result->account_state_->last_transaction_id_->lt_ transactionHash:makeData(result->account_state_->last_transaction_id_->hash_)];
@@ -611,42 +520,6 @@ typedef enum {
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
}
-- (MTSignal *)makeWalletInitialized:(TONKey *)key localPassword:(NSData *)localPassword {
- return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
- NSData *publicKeyData = [key.publicKey dataUsingEncoding:NSUTF8StringEncoding];
- if (publicKeyData == nil) {
- [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in exportKey"]];
- return [[MTBlockDisposable alloc] initWithBlock:^{}];
- }
-
- uint64_t requestId = _nextRequestId;
- _nextRequestId += 1;
-
- _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) {
- if (object->get_id() == tonlib_api::error::ID) {
- auto error = tonlib_api::move_object_as(object);
- [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]];
- } else {
- [subscriber putCompletion];
- }
- }];
-
- auto query = make_object(
- make_object(
- make_object(
- makeString(publicKeyData),
- makeSecureString(key.secret)
- ),
- makeSecureString(localPassword)
- )
- );
- _client->send({ requestId, std::move(query) });
-
- return [[MTBlockDisposable alloc] initWithBlock:^{
- }];
- }] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
-}
-
- (MTSignal *)importKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword wordList:(NSArray *)wordList {
return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
uint64_t requestId = _nextRequestId;
@@ -688,11 +561,11 @@ typedef enum {
}] startOn:[MTQueue mainQueue]] deliverOn:[MTQueue mainQueue]];
}
-- (MTSignal *)deleteKeyWithPublicKey:(NSString *)publicKey {
+- (MTSignal *)deleteKey:(TONKey *)key {
return [[[[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
- NSData *publicKeyData = [publicKey dataUsingEncoding:NSUTF8StringEncoding];
+ NSData *publicKeyData = [key.publicKey dataUsingEncoding:NSUTF8StringEncoding];
if (publicKeyData == nil) {
- [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in deleteKeyWithPublicKey"]];
+ [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in deleteKey"]];
return [[MTBlockDisposable alloc] initWithBlock:^{}];
}
@@ -709,7 +582,10 @@ typedef enum {
}];
auto query = make_object(
- makeString(publicKeyData)
+ make_object(
+ makeString(publicKeyData),
+ makeSecureString(key.secret)
+ )
);
_client->send({ requestId, std::move(query) });
diff --git a/submodules/SSignalKit/SwiftSignalKit/SwiftSignalKit.xcodeproj/xcshareddata/xcschemes/SwiftSignalKit.xcscheme b/submodules/SSignalKit/SwiftSignalKit/SwiftSignalKit.xcodeproj/xcshareddata/xcschemes/SwiftSignalKit.xcscheme
index 843e7441be..c82fe8764f 100644
--- a/submodules/SSignalKit/SwiftSignalKit/SwiftSignalKit.xcodeproj/xcshareddata/xcschemes/SwiftSignalKit.xcscheme
+++ b/submodules/SSignalKit/SwiftSignalKit/SwiftSignalKit.xcodeproj/xcshareddata/xcschemes/SwiftSignalKit.xcscheme
@@ -29,6 +29,8 @@
shouldUseLaunchSchemeArgsEnv = "YES">
+
+
+
+
+
+
+
+
deliverOnMainQueue).start(next: { wallets in
- if let tonContext = context.tonContext {
- if !wallets.wallets.isEmpty {
- let _ = (testGiverWalletAddress(tonInstance: tonContext.instance)
- |> deliverOnMainQueue).start(next: { address in
- arguments.pushController(WalletInfoScreen(context: context, tonContext: tonContext, walletInfo: wallets.wallets[0].info, address: address))
- })
- }
- }
- })
- })
- case let .getGrams(theme):
- return ItemListActionItem(theme: theme, title: "Update Wallet", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
- guard let context = arguments.context else {
- return
- }
- let _ = (availableWallets(postbox: context.account.postbox)
- |> deliverOnMainQueue).start(next: { wallets in
- if let tonContext = context.tonContext {
- if !wallets.wallets.isEmpty {
- let _ = (walletAddress(publicKey: wallets.wallets[0].info.publicKey, tonInstance: tonContext.instance)
- |> deliverOnMainQueue).start(next: { address in
- let _ = (getGramsFromTestGiver(address: address, amount: 1500000000, tonInstance: tonContext.instance)
- |> deliverOnMainQueue).start(completed: {
- let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
- let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .success)
- arguments.presentController(controller, nil)
- })
- })
- }
- }
- })
- })
case let .optimizeDatabase(theme):
return ItemListActionItem(theme: theme, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
guard let context = arguments.context else {
@@ -599,8 +552,6 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.resetData(presentationData.theme))
entries.append(.resetDatabase(presentationData.theme))
entries.append(.resetHoles(presentationData.theme))
- entries.append(.openDebugWallet(presentationData.theme))
- entries.append(.getGrams(presentationData.theme))
entries.append(.optimizeDatabase(presentationData.theme))
entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos))
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
diff --git a/submodules/TelegramCore/TelegramCore/Wallets.swift b/submodules/TelegramCore/TelegramCore/Wallets.swift
index b216bb67e0..b198a21183 100644
--- a/submodules/TelegramCore/TelegramCore/Wallets.swift
+++ b/submodules/TelegramCore/TelegramCore/Wallets.swift
@@ -186,7 +186,7 @@ public final class TonInstance {
self.impl.with { impl in
impl.withInstance { ton in
- let cancel = ton.getTestWalletAccountAddress(withPublicKey: publicKey.rawValue).start(next: { address in
+ let cancel = ton.getWalletAccountAddress(withPublicKey: publicKey.rawValue).start(next: { address in
guard let address = address as? String else {
return
}
@@ -205,32 +205,7 @@ public final class TonInstance {
}
}
- fileprivate func testGiverWalletAddress() -> Signal {
- return Signal { subscriber in
- let disposable = MetaDisposable()
-
- self.impl.with { impl in
- impl.withInstance { ton in
- let cancel = ton.getTestGiverAddress().start(next: { address in
- guard let address = address as? String else {
- return
- }
- subscriber.putNext(address)
- subscriber.putCompletion()
- }, error: { _ in
- }, completed: {
- })
- disposable.set(ActionDisposable {
- cancel?.dispose()
- })
- }
- }
-
- return disposable
- }
- }
-
- private func getWalletStateRaw(address: String) -> Signal {
+ private func getWalletStateRaw(address: String) -> Signal {
return Signal { subscriber in
let disposable = MetaDisposable()
@@ -242,6 +217,7 @@ public final class TonInstance {
}
subscriber.putNext(state)
}, error: { _ in
+ subscriber.putError(.generic)
}, completed: {
subscriber.putCompletion()
})
@@ -255,7 +231,7 @@ public final class TonInstance {
}
}
- fileprivate func getWalletState(address: String) -> Signal<(WalletState, Int64), NoError> {
+ fileprivate func getWalletState(address: String) -> Signal<(WalletState, Int64), GetWalletStateError> {
return self.getWalletStateRaw(address: address)
|> map { state in
return (WalletState(balance: state.balance, lastTransactionId: state.lastTransactionId.flatMap(WalletTransactionId.init(tonTransactionId:))), state.syncUtime)
@@ -314,40 +290,20 @@ public final class TonInstance {
}
}
- fileprivate func getTestGiverAccountState() -> Signal {
- return Signal { subscriber in
- let disposable = MetaDisposable()
-
- self.impl.with { impl in
- impl.withInstance { ton in
- let cancel = ton.getTestGiverAccountState().start(next: { state in
- subscriber.putNext(state as? TONAccountState)
- subscriber.putCompletion()
- }, error: { _ in
- subscriber.putNext(nil)
- subscriber.putCompletion()
- }, completed: {
- })
- }
- }
-
- return disposable
+ fileprivate func sendGramsFromWallet(keychain: TonKeychain, serverSalt: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: String) -> Signal {
+ return keychain.decrypt(walletInfo.encryptedSecret)
+ |> mapError { _ -> SendGramsFromWalletError in
+ return .secretDecryptionFailed
}
- }
-
- fileprivate func getGramsFromTestGiver(address: String, amount: Int64) -> Signal {
- return self.getTestGiverAccountState()
- |> castError(GetGramsFromTestGiverError.self)
- |> mapToSignal { state in
- guard let state = state else {
- return .fail(.generic)
- }
+ |> mapToSignal { decryptedSecret -> Signal in
+ let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret)
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
impl.withInstance { ton in
- let cancel = ton.testGiverSendGrams(with: state, accountAddress: address, amount: amount).start(next: { _ in
+ let cancel = ton.sendGrams(from: key, localPassword: serverSalt, fromAddress: fromAddress, toAddress: toAddress, amount: amount, textMessage: textMessage).start(next: { _ in
+ preconditionFailure()
}, error: { _ in
subscriber.putError(.generic)
}, completed: {
@@ -364,104 +320,6 @@ public final class TonInstance {
}
}
- private enum MakeWalletInitializedError {
- case generic
- }
-
- private func makeWalletInitialized(key: TONKey, serverSalt: Data) -> Signal {
- return Signal { subscriber in
- let disposable = MetaDisposable()
-
- self.impl.with { impl in
- impl.withInstance { ton in
- let cancel = ton.makeWalletInitialized(key, localPassword: serverSalt).start(next: { _ in
- preconditionFailure()
- }, error: { _ in
- subscriber.putError(.generic)
- }, completed: {
- subscriber.putCompletion()
- })
- disposable.set(ActionDisposable {
- cancel?.dispose()
- })
- }
- }
-
- return disposable
- }
- }
-
- private func ensureWalletInitialized(address: String, key: TONKey, serverSalt: Data) -> Signal {
- return self.getWalletStateRaw(address: address)
- |> castError(MakeWalletInitializedError.self)
- |> mapToSignal { state -> Signal in
- if state.isInitialized {
- return .complete()
- } else {
- let poll: Signal = self.getWalletStateRaw(address: address)
- |> castError(MakeWalletInitializedError.self)
- |> mapToSignal { state -> Signal in
- if state.isInitialized {
- return .single(Void())
- } else {
- return .complete()
- }
- }
-
- let pollUntilInitialized = (
- poll
- |> then(.complete()
- |> delay(2.0, queue: Queue.concurrentDefaultQueue()))
- )
- |> restart
- |> take(1)
-
- return self.makeWalletInitialized(key: key, serverSalt: serverSalt)
- |> then(
- pollUntilInitialized
- |> ignoreValues
- )
- }
- }
- }
-
- fileprivate func sendGramsFromWallet(keychain: TonKeychain, serverSalt: Data, walletInfo: WalletInfo, fromAddress: String, toAddress: String, amount: Int64, textMessage: String) -> Signal {
- return keychain.decrypt(walletInfo.encryptedSecret)
- |> mapError { _ -> SendGramsFromWalletError in
- return .secretDecryptionFailed
- }
- |> mapToSignal { decryptedSecret -> Signal in
- let key = TONKey(publicKey: walletInfo.publicKey.rawValue, secret: decryptedSecret)
-
- return self.ensureWalletInitialized(address: fromAddress, key: key, serverSalt: serverSalt)
- |> mapError { _ -> SendGramsFromWalletError in
- return .generic
- }
- |> then(
- 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).start(next: { _ in
- preconditionFailure()
- }, error: { _ in
- subscriber.putError(.generic)
- }, completed: {
- subscriber.putCompletion()
- })
- disposable.set(ActionDisposable {
- cancel?.dispose()
- })
- }
- }
-
- return disposable
- }
- )
- }
- }
-
fileprivate func walletRestoreWords(walletInfo: WalletInfo, keychain: TonKeychain, serverSalt: Data) -> Signal<[String], WalletRestoreWordsError> {
return keychain.decrypt(walletInfo.encryptedSecret)
|> mapError { _ -> WalletRestoreWordsError in
@@ -494,6 +352,35 @@ public final class TonInstance {
}
}
}
+
+ fileprivate func deleteLocalWalletData(walletInfo: WalletInfo, keychain: TonKeychain, serverSalt: Data) -> Signal {
+ return keychain.decrypt(walletInfo.encryptedSecret)
+ |> mapError { _ -> DeleteLocalWalletDataError in
+ return .secretDecryptionFailed
+ }
+ |> mapToSignal { decryptedSecret -> Signal 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
+ subscriber.putError(.generic)
+ }, completed: {
+ subscriber.putCompletion()
+ })
+ disposable.set(ActionDisposable {
+ cancel?.dispose()
+ })
+ }
+ }
+
+ return disposable
+ }
+ }
+ }
}
public struct WalletPublicKey: Codable, Hashable {
@@ -663,15 +550,35 @@ public func importWallet(postbox: Postbox, network: Network, tonInstance: TonIns
}
}
-public func debugDeleteWallets(postbox: Postbox) -> Signal {
- return postbox.transaction { transaction -> Void in
- transaction.updatePreferencesEntry(key: PreferencesKeys.walletCollection, { current in
- var walletCollection = (current as? WalletCollection) ?? WalletCollection(wallets: [])
- walletCollection.wallets = []
- return walletCollection
- })
+public enum DeleteLocalWalletDataError {
+ case generic
+ case secretDecryptionFailed
+}
+
+public func deleteLocalWalletData(postbox: Postbox, network: Network, tonInstance: TonInstance, keychain: TonKeychain, walletInfo: WalletInfo) -> Signal {
+ return getServerWalletSalt(network: network)
+ |> mapError { _ -> DeleteLocalWalletDataError in
+ return .generic
+ }
+ |> mapToSignal { serverSalt -> Signal 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
+ )
}
- |> ignoreValues
}
public enum WalletRestoreWordsError {
@@ -703,11 +610,11 @@ public func walletAddress(publicKey: WalletPublicKey, tonInstance: TonInstance)
return tonInstance.walletAddress(publicKey: publicKey)
}
-public func testGiverWalletAddress(tonInstance: TonInstance) -> Signal {
- return tonInstance.testGiverWalletAddress()
+private enum GetWalletStateError {
+ case generic
}
-private func getWalletState(address: String, tonInstance: TonInstance) -> Signal<(WalletState, Int64), NoError> {
+private func getWalletState(address: String, tonInstance: TonInstance) -> Signal<(WalletState, Int64), GetWalletStateError> {
return tonInstance.getWalletState(address: address)
}
@@ -738,7 +645,9 @@ public func getCombinedWalletState(postbox: Postbox, walletInfo: WalletInfo, ton
|> castError(GetCombinedWalletStateError.self)
|> mapToSignal { address -> Signal in
return getWalletState(address: address, tonInstance: tonInstance)
- |> castError(GetCombinedWalletStateError.self)
+ |> mapError { _ -> GetCombinedWalletStateError in
+ return .generic
+ }
|> mapToSignal { walletState, syncUtime -> Signal in
let topTransactions: Signal<[WalletTransaction], GetCombinedWalletStateError>
if walletState.lastTransactionId == cachedState?.walletState.lastTransactionId {
@@ -772,14 +681,6 @@ public func getCombinedWalletState(postbox: Postbox, walletInfo: WalletInfo, ton
}
}
-public enum GetGramsFromTestGiverError {
- case generic
-}
-
-public func getGramsFromTestGiver(address: String, amount: Int64, tonInstance: TonInstance) -> Signal {
- return tonInstance.getGramsFromTestGiver(address: address, amount: amount)
-}
-
public enum SendGramsFromWalletError {
case generic
case secretDecryptionFailed
diff --git a/submodules/TelegramUI/Images.xcassets/Wallet/RefreshIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Wallet/RefreshIcon.imageset/Contents.json
new file mode 100644
index 0000000000..f35b36343a
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Wallet/RefreshIcon.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "􀊯 2.pdf"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/submodules/TelegramUI/Images.xcassets/Wallet/RefreshIcon.imageset/􀊯 2.pdf b/submodules/TelegramUI/Images.xcassets/Wallet/RefreshIcon.imageset/􀊯 2.pdf
new file mode 100644
index 0000000000..d557394c0a
Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Wallet/RefreshIcon.imageset/􀊯 2.pdf differ
diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
index 4ccaa6a4e9..a78fafa318 100644
--- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
+++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift
@@ -600,7 +600,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var effectiveInputNodeHeight: CGFloat?
if let inputNodeHeightAndOverflow = inputNodeHeightAndOverflow {
if let upperInputPositionBound = self.upperInputPositionBound {
- effectiveInputNodeHeight = min(layout.size.height - max(0.0, upperInputPositionBound), inputNodeHeightAndOverflow.0)
+ effectiveInputNodeHeight = max(0.0, min(layout.size.height - max(0.0, upperInputPositionBound), inputNodeHeightAndOverflow.0))
} else {
effectiveInputNodeHeight = inputNodeHeightAndOverflow.0
}
diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/SendingGrams.tgs b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/SendingGrams.tgs
new file mode 100644
index 0000000000..9876c9658e
Binary files /dev/null and b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/SendingGrams.tgs differ
diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletCreated.tgs b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletCreated.tgs
new file mode 100644
index 0000000000..f5bac9d80b
Binary files /dev/null and b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletCreated.tgs differ
diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletEmpty.tgs b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletEmpty.tgs
new file mode 100644
index 0000000000..b33b54599f
Binary files /dev/null and b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletEmpty.tgs differ
diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletIntroStatic.tgs b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletIntroStatic.tgs
new file mode 100644
index 0000000000..63e8e22514
Binary files /dev/null and b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletIntroStatic.tgs differ
diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordCheck.tgs b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordCheck.tgs
new file mode 100644
index 0000000000..9008fc51e2
Binary files /dev/null and b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordCheck.tgs differ
diff --git a/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordList.tgs b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordList.tgs
new file mode 100644
index 0000000000..278b1531b6
Binary files /dev/null and b/submodules/TelegramUI/TelegramUI/Resources/WalletAnimations/WalletWordList.tgs differ
diff --git a/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift b/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift
index e4411116b0..e2b570db60 100644
--- a/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift
+++ b/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift
@@ -6,15 +6,18 @@ import TelegramPresentationData
import TelegramCore
import AnimationUI
import SwiftSignalKit
+import AppBundle
class WalletInfoEmptyItem: ListViewItem {
+ let account: Account
let theme: PresentationTheme
let strings: PresentationStrings
let address: String
let selectable: Bool = false
- init(theme: PresentationTheme, strings: PresentationStrings, address: String) {
+ init(account: Account, theme: PresentationTheme, strings: PresentationStrings, address: String) {
+ self.account = account
self.theme = theme
self.strings = strings
self.address = address
@@ -22,7 +25,7 @@ class WalletInfoEmptyItem: ListViewItem {
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) {
async {
- let node = WalletInfoEmptyItemNode()
+ let node = WalletInfoEmptyItemNode(account: self.account)
node.insets = UIEdgeInsets()
node.layoutForParams(params, item: self, previousItem: previousItem, nextItem: nextItem)
Queue.mainQueue().async {
@@ -54,21 +57,20 @@ class WalletInfoEmptyItem: ListViewItem {
final class WalletInfoEmptyItemNode: ListViewItemNode {
private let offsetContainer: ASDisplayNode
- private let iconNode: ASImageNode
private let animationNode: AnimatedStickerNode
private let titleNode: TextNode
private let textNode: TextNode
private let addressNode: TextNode
- init() {
+ init(account: Account) {
self.offsetContainer = ASDisplayNode()
- self.iconNode = ASImageNode()
- self.iconNode.displayWithoutProcessing = true
- self.iconNode.displaysAsynchronously = false
- self.iconNode.image = UIImage(bundleImageName: "Wallet/DuckIcon")
-
self.animationNode = AnimatedStickerNode()
+ if let path = getAppBundle().path(forResource: "WalletEmpty", ofType: "tgs") {
+ self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, mode: .direct)
+ self.animationNode.visibility = true
+ }
+ self.animationNode.visibility = true
self.titleNode = TextNode()
self.titleNode.displaysAsynchronously = false
@@ -79,7 +81,6 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
self.wantsTrailingItemSpaceUpdates = true
- self.offsetContainer.addSubnode(self.iconNode)
self.offsetContainer.addSubnode(self.animationNode)
self.offsetContainer.addSubnode(self.titleNode)
self.offsetContainer.addSubnode(self.textNode)
@@ -101,8 +102,8 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
let termsSpacing: CGFloat = 11.0
let buttonHeight: CGFloat = 50.0
- let iconSize: CGSize
- iconSize = self.iconNode.image?.size ?? CGSize(width: 140.0, height: 140.0)
+ let iconSize = CGSize(width: 140.0, height: 140.0)
+ self.animationNode.updateLayout(size: iconSize)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode)
@@ -145,7 +146,7 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
let transition: ContainedViewLayoutTransition = .immediate
- transition.updateFrameAdditive(node: strongSelf.iconNode, frame: iconFrame)
+ transition.updateFrameAdditive(node: strongSelf.animationNode, frame: iconFrame)
strongSelf.animationNode.updateLayout(size: iconFrame.size)
transition.updateFrameAdditive(node: strongSelf.animationNode, frame: iconFrame)
transition.updateFrameAdditive(node: strongSelf.titleNode, frame: titleFrame)
diff --git a/submodules/WalletUI/Sources/WalletInfoScreen.swift b/submodules/WalletUI/Sources/WalletInfoScreen.swift
index fd3a253f1c..0ec3c238c6 100644
--- a/submodules/WalletUI/Sources/WalletInfoScreen.swift
+++ b/submodules/WalletUI/Sources/WalletInfoScreen.swift
@@ -8,63 +8,10 @@ import Display
import Postbox
import TelegramCore
import SolidRoundedButtonNode
-import AnimationUI
import SwiftSignalKit
import MergeLists
import TelegramStringFormatting
-private func stringForRelativeUpdateTime(strings: PresentationStrings, day: RelativeTimestampFormatDay, dateTimeFormat: PresentationDateTimeFormat, hours: Int32, minutes: Int32) -> String {
- let dayString: String
- switch day {
- case .today:
- dayString = strings.Updated_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0
- case .yesterday:
- dayString = strings.Updated_YesterdayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0
- }
- return dayString
-}
-
-private func lastUpdateTimestampString(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, statusTimestamp: Int32, relativeTo timestamp: Int32) -> String {
- let difference = timestamp - statusTimestamp
- let expanded = true
- if difference < 60 {
- return strings.Updated_JustNow
- } else if difference < 60 * 60 && !expanded {
- let minutes = difference / 60
- return strings.Updated_MinutesAgo(minutes)
- } else {
- var t: time_t = time_t(statusTimestamp)
- var timeinfo: tm = tm()
- localtime_r(&t, &timeinfo)
-
- var now: time_t = time_t(timestamp)
- var timeinfoNow: tm = tm()
- localtime_r(&now, &timeinfoNow)
-
- if timeinfo.tm_year != timeinfoNow.tm_year {
- return strings.Updated_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).0
- }
-
- let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday
- if dayDifference == 0 || dayDifference == -1 {
- let day: RelativeTimestampFormatDay
- if dayDifference == 0 {
- if expanded {
- day = .today
- } else {
- let minutes = difference / (60 * 60)
- return strings.Updated_HoursAgo(minutes)
- }
- } else {
- day = .yesterday
- }
- return stringForRelativeUpdateTime(strings: strings, day: day, dateTimeFormat: dateTimeFormat, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min)
- } else {
- return strings.Updated_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).0
- }
- }
-}
-
public final class WalletInfoScreen: ViewController {
private let context: AccountContext
private let tonContext: TonContext
@@ -132,6 +79,11 @@ public final class WalletInfoScreen: ViewController {
return
}
strongSelf.push(walletTransactionInfoController(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: strongSelf.walletInfo, walletTransaction: transaction))
+ }, present: { [weak self] c in
+ guard let strongSelf = self else {
+ return
+ }
+ strongSelf.present(c, in: .window(.root))
})
self.displayNodeDidLoad()
@@ -147,22 +99,39 @@ public final class WalletInfoScreen: ViewController {
}
private final class WalletInfoBalanceNode: ASDisplayNode {
- let balanceTextNode: ImmediateTextNode
+ let balanceIntegralTextNode: ImmediateTextNode
+ let balanceFractionalTextNode: ImmediateTextNode
let balanceIconNode: ASImageNode
var balance: String = " " {
didSet {
- self.balanceTextNode.attributedText = NSAttributedString(string: self.balance, font: Font.bold(39.0), textColor: .white)
+ let integralString = NSMutableAttributedString()
+ let fractionalString = NSMutableAttributedString()
+ if let range = self.balance.range(of: ".") {
+ let integralPart = String(self.balance[.. CGFloat {
+ func update(width: CGFloat, scaleTransition: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let sideInset: CGFloat = 16.0
let balanceIconSpacing: CGFloat = 8.0
- let balanceTextSize = self.balanceTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
+ let balanceIntegralTextSize = self.balanceIntegralTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
+ let balanceFractionalTextSize = self.balanceFractionalTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: 200.0))
let balanceIconSize = self.balanceIconNode.image?.size ?? CGSize(width: 38.0, height: 34.0)
- let balanceOrigin = CGPoint(x: floor((width - balanceTextSize.width - balanceIconSpacing - balanceIconSize.width / 2.0) / 2.0), y: 0.0)
- let balanceTextFrame = CGRect(origin: balanceOrigin, size: balanceTextSize)
+ let fractionalScale: CGFloat = scaleTransition * 0.5 + (1.0 - scaleTransition) * 1.0
+
+ let balanceOrigin = CGPoint(x: floor((width - balanceIntegralTextSize.width - balanceFractionalTextSize.width * fractionalScale - balanceIconSpacing - balanceIconSize.width / 2.0) / 2.0), y: 0.0)
+
+ let balanceIntegralTextFrame = CGRect(origin: balanceOrigin, size: balanceIntegralTextSize)
+ var balanceFractionalTextFrame = CGRect(origin: CGPoint(x: balanceIntegralTextFrame.maxX, y: balanceIntegralTextFrame.maxY - balanceFractionalTextSize.height), size: balanceFractionalTextSize)
+ let apparentBalanceFractionalTextFrame = CGRect(origin: balanceFractionalTextFrame.origin, size: CGSize(width: balanceFractionalTextFrame.width * fractionalScale, height: balanceFractionalTextFrame.height * fractionalScale))
+ balanceFractionalTextFrame.origin.x -= balanceFractionalTextFrame.width / 2.0 * (1.0 - fractionalScale)
+ balanceFractionalTextFrame.origin.y += balanceFractionalTextFrame.height / 4.0 * (1.0 - fractionalScale)
+
let balanceIconFrame: CGRect
- balanceIconFrame = CGRect(origin: CGPoint(x: balanceTextFrame.maxX + balanceIconSpacing, y: balanceTextFrame.minY + floor((balanceTextFrame.height - balanceIconSize.height) / 2.0)), size: balanceIconSize)
- transition.updateFrameAdditive(node: self.balanceTextNode, frame: balanceTextFrame)
+ balanceIconFrame = CGRect(origin: CGPoint(x: apparentBalanceFractionalTextFrame.maxX + balanceIconSpacing, y: balanceIntegralTextFrame.minY + floor((balanceIntegralTextFrame.height - balanceIconSize.height) / 2.0)), size: balanceIconSize)
+
+ transition.updateFrameAdditive(node: self.balanceIntegralTextNode, frame: balanceIntegralTextFrame)
+ transition.updateFrameAsPositionAndBounds(node: self.balanceFractionalTextNode, frame: balanceFractionalTextFrame)
+ transition.updateTransformScale(node: self.balanceFractionalTextNode, scale: fractionalScale)
transition.updateFrame(node: self.balanceIconNode, frame: balanceIconFrame)
- return balanceTextSize.height
+ return balanceIntegralTextSize.height
}
}
private final class WalletInfoHeaderNode: ASDisplayNode {
var balance: Int64?
var isRefreshing: Bool = false
-
- var timestampString: String = "" {
- didSet {
- self.balanceTimestampNode.attributedText = NSAttributedString(string: self.timestampString, font: Font.regular(13), textColor: UIColor(white: 1.0, alpha: 0.6))
- }
- }
+ var timestamp: Int32?
let balanceNode: WalletInfoBalanceNode
- private let refreshNode: AnimatedStickerNode
+ private let refreshNode: WalletRefreshNode
private let balanceSubtitleNode: ImmediateTextNode
- private let balanceTimestampNode: ImmediateTextNode
private let receiveButtonNode: SolidRoundedButtonNode
private let sendButtonNode: SolidRoundedButtonNode
private let headerBackgroundNode: ASImageNode
- init(account: Account, theme: PresentationTheme, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
- self.balanceNode = WalletInfoBalanceNode(theme: theme)
+ init(account: Account, presentationData: PresentationData, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
+ self.balanceNode = WalletInfoBalanceNode(theme: presentationData.theme)
self.balanceSubtitleNode = ImmediateTextNode()
self.balanceSubtitleNode.displaysAsynchronously = false
self.balanceSubtitleNode.attributedText = NSAttributedString(string: "your balance", font: Font.regular(13), textColor: UIColor(white: 1.0, alpha: 0.6))
- self.balanceTimestampNode = ImmediateTextNode()
- self.balanceTimestampNode.displaysAsynchronously = false
- self.balanceTimestampNode.attributedText = NSAttributedString(string: "", font: Font.regular(13), textColor: UIColor(white: 1.0, alpha: 0.6))
-
self.headerBackgroundNode = ASImageNode()
self.headerBackgroundNode.displaysAsynchronously = false
self.headerBackgroundNode.displayWithoutProcessing = true
@@ -237,12 +209,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
self.receiveButtonNode = SolidRoundedButtonNode(title: "Receive", icon: UIImage(bundleImageName: "Wallet/ReceiveButtonIcon"), theme: SolidRoundedButtonTheme(backgroundColor: .white, foregroundColor: .black), height: 50.0, cornerRadius: 10.0, gloss: false)
self.sendButtonNode = SolidRoundedButtonNode(title: "Send", icon: UIImage(bundleImageName: "Wallet/SendButtonIcon"), theme: SolidRoundedButtonTheme(backgroundColor: .white, foregroundColor: .black), height: 50.0, cornerRadius: 10.0, gloss: false)
- self.refreshNode = AnimatedStickerNode()
- self.refreshNode.playToCompletionOnStop = true
- self.refreshNode.automaticallyLoadFirstFrame = true
- if let path = getAppBundle().path(forResource: "celebrate", ofType: "tgs") {
- self.refreshNode.setup(account: account, resource: .localFile(path), width: Int(32.0 * UIScreenScale), height: Int(32.0 * UIScreenScale), mode: .direct)
- }
+ self.refreshNode = WalletRefreshNode(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
super.init()
@@ -251,7 +218,6 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
self.addSubnode(self.sendButtonNode)
self.addSubnode(self.balanceNode)
self.addSubnode(self.balanceSubtitleNode)
- self.addSubnode(self.balanceTimestampNode)
self.addSubnode(self.refreshNode)
self.receiveButtonNode.pressed = {
@@ -284,9 +250,10 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
let buttonAlpha = buttonTransition * 1.0
let balanceSubtitleSize = self.balanceSubtitleNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: 200.0))
- let balanceTimestampSize = self.balanceTimestampNode.updateLayout(CGSize(width: size.width - sideInset * 2.0, height: 200.0))
- let balanceHeight = self.balanceNode.update(width: size.width, transition: transition)
+ let headerScaleTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minHeaderOffset) / (maxHeaderOffset - minHeaderOffset)))
+
+ let balanceHeight = self.balanceNode.update(width: size.width, scaleTransition: headerScaleTransition, transition: transition)
let balanceSize = CGSize(width: size.width, height: balanceHeight)
let maxHeaderScale: CGFloat = min(1.0, (size.width - 40.0) / balanceSize.width)
@@ -296,29 +263,21 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
let minHeaderY = navigationHeight - 44.0 + floor((44.0 - minHeaderHeight) / 2.0)
let maxHeaderY = floor((size.height - balanceSize.height) / 2.0 - balanceSubtitleSize.height)
- let headerScaleTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minHeaderOffset) / (maxHeaderOffset - minHeaderOffset)))
let headerPositionTransition: CGFloat = max(0.0, (effectiveOffset - minHeaderOffset) / (maxOffset - minHeaderOffset))
let headerY = headerPositionTransition * maxHeaderY + (1.0 - headerPositionTransition) * minHeaderY
let headerScale = headerScaleTransition * maxHeaderScale + (1.0 - headerScaleTransition) * minHeaderScale
- let refreshSize = CGSize(width: 32.0, height: 32.0)
- self.refreshNode.updateLayout(size: refreshSize)
+ let refreshSize = CGSize(width: 0.0, height: 0.0)
transition.updateFrame(node: self.refreshNode, frame: CGRect(origin: CGPoint(x: floor((size.width - refreshSize.width) / 2.0), y: navigationHeight - 44.0 + floor((44.0 - refreshSize.height) / 2.0)), size: refreshSize))
+ transition.updateAlpha(node: self.refreshNode, alpha: headerScaleTransition)
if self.balance == nil {
- transition.updateAlpha(node: self.refreshNode, alpha: 0.0)
- transition.updateSublayerTransformScale(node: self.refreshNode, scale: 0.1)
- self.refreshNode.visibility = false
+ self.refreshNode.update(state: .pullToRefresh(self.timestamp ?? 0, 0.0))
} else if self.isRefreshing {
- transition.updateAlpha(node: self.refreshNode, alpha: 1.0)
- transition.updateSublayerTransformScale(node: self.refreshNode, scale: 1.0)
- self.refreshNode.visibility = true
+ self.refreshNode.update(state: .refreshing)
} else {
let refreshOffset: CGFloat = 20.0
- let refreshScaleTransition: CGFloat = max(0.0, min(1.0, (offset - maxOffset) / refreshOffset))
- transition.updateAlpha(node: self.refreshNode, alpha: refreshScaleTransition)
- let refreshScale: CGFloat = refreshScaleTransition * 1.0 + (1.0 - refreshScaleTransition) * 0.1
- transition.updateSublayerTransformScale(node: self.refreshNode, scale: refreshScale)
- self.refreshNode.visibility = false
+ let refreshScaleTransition: CGFloat = max(0.0, (offset - maxOffset) / refreshOffset)
+ self.refreshNode.update(state: .pullToRefresh(self.timestamp ?? 0, refreshScaleTransition * 0.25))
}
let balanceFrame = CGRect(origin: CGPoint(x: 0.0, y: headerY), size: balanceSize)
@@ -328,11 +287,6 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
let balanceSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - balanceSubtitleSize.width) / 2.0), y: balanceFrame.midY + (balanceFrame.height / 2.0 * headerScale) + balanceSubtitleSpacing), size: balanceSubtitleSize)
transition.updateFrameAdditive(node: self.balanceSubtitleNode, frame: balanceSubtitleFrame)
- let balanceTimestampFrame = CGRect(origin: CGPoint(x: floor((size.width - balanceTimestampSize.width) / 2.0), y: balanceSubtitleFrame.maxY + 2.0), size: balanceTimestampSize)
- transition.updateFrameAdditive(node: self.balanceTimestampNode, frame: balanceTimestampFrame)
-
- transition.updateAlpha(node: self.balanceTimestampNode, alpha: headerScaleTransition)
-
let headerHeight: CGFloat = 1000.0
transition.updateFrame(node: self.headerBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: effectiveOffset + 10.0 - headerHeight), size: CGSize(width: size.width, height: headerHeight)))
@@ -360,11 +314,11 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
if self.balance == nil {
self.balanceNode.isHidden = true
self.balanceSubtitleNode.isHidden = true
- self.balanceTimestampNode.isHidden = true
+ self.refreshNode.isHidden = true
} else {
self.balanceNode.isHidden = false
self.balanceSubtitleNode.isHidden = false
- self.balanceTimestampNode.isHidden = false
+ self.refreshNode.isHidden = false
}
transition.updateFrame(node: self.receiveButtonNode, frame: receiveButtonFrame)
transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha)
@@ -390,14 +344,10 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
self.receiveButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.balanceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.balanceSubtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
- self.balanceTimestampNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ self.refreshNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
self.balanceNode.isLoading = false
}
-
- func animateBeganRefreshing() {
- //self.refreshNode.layer.animate(from: 0.5 as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2, delay: 0.0, removeOnCompletion: true, additive: true)
- }
}
private struct WalletInfoListTransaction {
@@ -443,10 +393,10 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
- func item(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, action: @escaping (WalletTransaction) -> Void) -> ListViewItem {
+ func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, action: @escaping (WalletTransaction) -> Void) -> ListViewItem {
switch self {
case let .empty(address):
- return WalletInfoEmptyItem(theme: theme, strings: strings, address: address)
+ return WalletInfoEmptyItem(account: account, theme: theme, strings: strings, address: address)
case let .transaction(_, transaction):
return WalletInfoTransactionItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, walletTransaction: transaction, action: {
action(transaction)
@@ -455,12 +405,12 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
-private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], presentationData: PresentationData, action: @escaping (WalletTransaction) -> Void) -> WalletInfoListTransaction {
+private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], account: Account, presentationData: PresentationData, action: @escaping (WalletTransaction) -> Void) -> WalletInfoListTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
- let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action), directionHint: nil) }
- let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action), directionHint: nil) }
+ let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action), directionHint: nil) }
+ let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action), directionHint: nil) }
return WalletInfoListTransaction(deletions: deletions, insertions: insertions, updates: updates)
}
@@ -473,6 +423,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private let address: String
private let openTransaction: (WalletTransaction) -> Void
+ private let present: (ViewController) -> Void
private let hapticFeedback = HapticFeedback()
@@ -502,15 +453,16 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private var updateTimestampTimer: SwiftSignalKit.Timer?
- init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void) {
+ init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void, present: @escaping (ViewController) -> Void) {
self.account = account
self.tonContext = tonContext
self.presentationData = presentationData
self.walletInfo = walletInfo
self.address = address
self.openTransaction = openTransaction
+ self.present = present
- self.headerNode = WalletInfoHeaderNode(account: account, theme: presentationData.theme, sendAction: sendAction, receiveAction: receiveAction)
+ self.headerNode = WalletInfoHeaderNode(account: account, presentationData: presentationData, sendAction: sendAction, receiveAction: receiveAction)
self.listNode = ListView()
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
@@ -521,7 +473,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
super.init()
- self.backgroundColor = .white
+ self.backgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor
self.addSubnode(self.listNode)
self.addSubnode(self.headerNode)
@@ -548,8 +500,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
if !strongSelf.reloadingState && canBeginRefresh && isScrolling {
if offset >= headerHeight + 100.0 {
canBeginRefresh = false
- strongSelf.headerNode.isRefreshing = true
- strongSelf.headerNode.animateBeganRefreshing()
strongSelf.hapticFeedback.impact()
strongSelf.refreshTransactions()
}
@@ -596,13 +546,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
guard let strongSelf = self, let combinedState = strongSelf.combinedState, !strongSelf.reloadingState else {
return
}
- let string = lastUpdateTimestampString(strings: strongSelf.presentationData.strings, dateTimeFormat: strongSelf.presentationData.dateTimeFormat, statusTimestamp: Int32(clamping: combinedState.timestamp), relativeTo: Int32(Date().timeIntervalSince1970))
- if strongSelf.headerNode.timestampString != string {
- strongSelf.headerNode.timestampString = string
- if let (layout, navigationHeight) = strongSelf.validLayout {
- strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate)
- }
- }
+ strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
}, queue: .mainQueue())
self.updateTimestampTimer?.start()
}
@@ -687,10 +631,9 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.loadingMoreTransactions = true
self.reloadingState = true
- self.headerNode.timestampString = "updating"
+ self.headerNode.isRefreshing = true
self.stateDisposable.set((getCombinedWalletState(postbox: self.account.postbox, walletInfo: self.walletInfo, tonInstance: self.tonContext.instance)
- |> delay(self.combinedState == nil ? 0.0 : 2.0, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
@@ -698,6 +641,9 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
let combinedState: CombinedWalletState?
switch value {
case let .cached(state):
+ if strongSelf.combinedState != nil {
+ return
+ }
if state == nil {
strongSelf.loadingIndicator.startAnimating()
} else {
@@ -722,7 +668,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.reloadingState = false
- strongSelf.headerNode.timestampString = lastUpdateTimestampString(strings: strongSelf.presentationData.strings, dateTimeFormat: strongSelf.presentationData.dateTimeFormat, statusTimestamp: Int32(clamping: combinedState.timestamp), relativeTo: Int32(Date().timeIntervalSince1970))
+ strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate)
@@ -756,6 +702,35 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
guard let strongSelf = self else {
return
}
+
+ strongSelf.reloadingState = false
+
+ if let combinedState = strongSelf.combinedState {
+ strongSelf.headerNode.timestamp = Int32(clamping: combinedState.timestamp)
+ }
+
+ if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
+ strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: strongSelf.listOffset ?? 0.0, transition: .immediate)
+ }
+
+ strongSelf.loadingMoreTransactions = false
+ strongSelf.canLoadMoreTransactions = false
+
+ 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))
+ }
+
+ if !strongSelf.didSetContentReady {
+ strongSelf.didSetContentReady = true
+ strongSelf.contentReady.set(.single(true))
+ }
+
+ strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: "An Error Occurred", text: "The wallet state can not be retrieved at this time. Please try again later.", actions: [
+ TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
+ })
+ ], actionLayout: .vertical))
}))
}
@@ -831,7 +806,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
}
}
- let transaction = preparedTransition(from: self.currentEntries ?? [], to: updatedEntries, presentationData: self.presentationData, action: { [weak self] transaction in
+ let transaction = preparedTransition(from: self.currentEntries ?? [], to: updatedEntries, account: self.account, presentationData: self.presentationData, action: { [weak self] transaction in
guard let strongSelf = self else {
return
}
diff --git a/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift b/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift
index fbddade9bc..89e138ea3d 100644
--- a/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift
+++ b/submodules/WalletUI/Sources/WalletInfoTransactionItem.swift
@@ -112,7 +112,6 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
- self.backgroundNode.backgroundColor = .white
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
@@ -228,7 +227,17 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
let (directionLayout, directionApply) = makeDirectionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: directionText, font: directionFont, textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
- let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftInset - 20.0 - dateLayout.size.width - directionLayout.size.width - iconSize.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
+ let titleString = NSMutableAttributedString()
+ if let range = title.range(of: ".") {
+ let integralPart = String(title[.. String {
+ let dayString: String
+ switch day {
+ case .today:
+ dayString = strings.Updated_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0
+ case .yesterday:
+ dayString = strings.Updated_YesterdayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0
+ }
+ return dayString
+}
+
+private func lastUpdateTimestampString(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, statusTimestamp: Int32, relativeTo timestamp: Int32) -> String {
+ let difference = timestamp - statusTimestamp
+ let expanded = true
+ if difference < 60 {
+ return strings.Updated_JustNow
+ } else if difference < 60 * 60 && !expanded {
+ let minutes = difference / 60
+ return strings.Updated_MinutesAgo(minutes)
+ } else {
+ var t: time_t = time_t(statusTimestamp)
+ var timeinfo: tm = tm()
+ localtime_r(&t, &timeinfo)
+
+ var now: time_t = time_t(timestamp)
+ var timeinfoNow: tm = tm()
+ localtime_r(&now, &timeinfoNow)
+
+ if timeinfo.tm_year != timeinfoNow.tm_year {
+ return strings.Updated_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).0
+ }
+
+ let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday
+ if dayDifference == 0 || dayDifference == -1 {
+ let day: RelativeTimestampFormatDay
+ if dayDifference == 0 {
+ if expanded {
+ day = .today
+ } else {
+ let minutes = difference / (60 * 60)
+ return strings.Updated_HoursAgo(minutes)
+ }
+ } else {
+ day = .yesterday
+ }
+ return stringForRelativeUpdateTime(strings: strings, day: day, dateTimeFormat: dateTimeFormat, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min)
+ } else {
+ return strings.Updated_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).0
+ }
+ }
+}
+
+enum WalletRefreshState: Equatable {
+ case pullToRefresh(Int32, CGFloat)
+ case refreshing
+}
+
+final class WalletRefreshNode: ASDisplayNode {
+ private let strings: PresentationStrings
+ private let dateTimeFormat: PresentationDateTimeFormat
+ private let iconContainer: ASDisplayNode
+ private let iconNode: ASImageNode
+ private let titleNode: ImmediateTextNode
+
+ private var state: WalletRefreshState?
+
+ private let animator: ConstantDisplayLinkAnimator
+
+ init(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) {
+ self.strings = strings
+ self.dateTimeFormat = dateTimeFormat
+
+ self.iconContainer = ASDisplayNode()
+
+ self.iconNode = ASImageNode()
+ self.iconNode.displaysAsynchronously = false
+ self.iconNode.displayWithoutProcessing = true
+ self.iconNode.image = UIImage(bundleImageName: "Wallet/RefreshIcon")
+ if let image = self.iconNode.image {
+ self.iconNode.frame = CGRect(origin: CGPoint(), size: image.size)
+ }
+
+ self.titleNode = ImmediateTextNode()
+ self.titleNode.displaysAsynchronously = false
+
+ var updateImpl: (() -> Void)?
+ self.animator = ConstantDisplayLinkAnimator(update: {
+ updateImpl?()
+ })
+
+ super.init()
+
+ self.iconContainer.addSubnode(self.iconNode)
+ self.addSubnode(self.iconContainer)
+ self.addSubnode(self.titleNode)
+
+ updateImpl = { [weak self] in
+ self?.updateAnimation()
+ }
+ }
+
+ private var currentAngle: CGFloat = 0.0
+ private var currentExtraSpeed: CGFloat = 0.0
+ private var animateToZeroState: (Double, CGFloat)?
+
+ private func updateAnimation() {
+ guard let state = self.state else {
+ return
+ }
+
+ var speed: CGFloat = 0.0
+ var baseValue: CGFloat = 0.0
+
+ switch state {
+ case .refreshing:
+ speed = 0.01
+ self.animateToZeroState = nil
+ case let .pullToRefresh(_, value):
+ if self.currentExtraSpeed.isZero && self.animateToZeroState == nil && !self.currentAngle.isZero {
+ self.animateToZeroState = (CACurrentMediaTime(), self.currentAngle)
+ }
+ if self.animateToZeroState == nil {
+ baseValue = value
+ }
+ }
+
+ if let (startTime, startValue) = self.animateToZeroState {
+ let endValue: CGFloat = floor(startValue) + 1.0
+ let duration: Double = Double(endValue - startValue) * 1.0
+ let timeDelta = (startTime + duration - CACurrentMediaTime())
+ let t: CGFloat = 1.0 - CGFloat(max(0.0, min(1.0, timeDelta / duration)))
+ if t >= 1.0 - CGFloat.ulpOfOne {
+ self.animateToZeroState = nil
+ self.currentAngle = 0.0
+ } else {
+ let bt = bezierPoint(0.23, 1.0, 0.32, 1.0, t)
+ self.currentAngle = startValue * (1.0 - bt) + endValue * bt
+ }
+ } else {
+ self.currentAngle += speed + self.currentExtraSpeed
+ }
+ self.currentExtraSpeed *= 0.97
+ if abs(self.currentExtraSpeed) < 0.0001 {
+ self.currentExtraSpeed = 0.0
+ }
+
+ self.iconNode.layer.transform = CATransform3DMakeRotation((baseValue + self.currentAngle) * CGFloat.pi * 2.0, 0.0, 0.0, 1.0)
+
+ if !self.currentExtraSpeed.isZero || !speed.isZero || self.animateToZeroState != nil {
+ self.animator.isPaused = false
+ } else {
+ self.animator.isPaused = true
+ }
+ }
+
+ func update(state: WalletRefreshState) {
+ if self.state == state {
+ return
+ }
+ let previousState = self.state
+ self.state = state
+
+ var pullProgress: CGFloat = 0.0
+
+ let title: String
+ switch state {
+ case let .pullToRefresh(ts, progress):
+ title = lastUpdateTimestampString(strings: self.strings, dateTimeFormat: dateTimeFormat, statusTimestamp: ts, relativeTo: Int32(Date().timeIntervalSince1970))
+ pullProgress = progress
+ case .refreshing:
+ title = "updating"
+ }
+
+ if let previousState = previousState {
+ switch state {
+ case .refreshing:
+ switch previousState {
+ case .refreshing:
+ break
+ default:
+ self.currentExtraSpeed = 0.05
+ }
+ default:
+ self.currentExtraSpeed = 0.0
+ }
+ }
+
+ self.updateAnimation()
+
+ self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(13.0), textColor: UIColor(white: 1.0, alpha: 0.6))
+
+ 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 contentWidth = iconSize.width + titleSize.width + iconSpacing
+ let contentOrigin = floor(-contentWidth / 2.0)
+
+ self.iconContainer.frame = CGRect(origin: CGPoint(x: contentOrigin, y: floor(-iconSize.height / 2.0)), size: iconSize)
+ self.titleNode.frame = CGRect(origin: CGPoint(x: contentOrigin + iconSize.width + iconSpacing, y: floor(-titleSize.height / 2.0)), size: titleSize)
+ }
+}
diff --git a/submodules/WalletUI/Sources/WalletSettingsScreen.swift b/submodules/WalletUI/Sources/WalletSettingsScreen.swift
index ee844ec86b..c691f15d90 100644
--- a/submodules/WalletUI/Sources/WalletSettingsScreen.swift
+++ b/submodules/WalletUI/Sources/WalletSettingsScreen.swift
@@ -104,11 +104,10 @@ public func walletSettingsController(context: AccountContext, tonContext: TonCon
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Delete Wallet", color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
- let _ = (debugDeleteWallets(postbox: context.account.postbox)
- |> deliverOnMainQueue).start(completed: {
- if let tonContext = context.tonContext {
- replaceAllWalletControllersImpl?(WalletSplashScreen(context: context, tonContext: tonContext, mode: .intro))
- }
+ let _ = (deleteLocalWalletData(postbox: context.account.postbox, network: context.account.network, tonInstance: tonContext.instance, keychain: tonContext.keychain, walletInfo: walletInfo)
+ |> deliverOnMainQueue).start(error: { _ in
+ }, completed: {
+ replaceAllWalletControllersImpl?(WalletSplashScreen(context: context, tonContext: tonContext, mode: .intro))
})
})
]), ActionSheetItemGroup(items: [
diff --git a/submodules/WalletUI/Sources/WalletSplashScreen.swift b/submodules/WalletUI/Sources/WalletSplashScreen.swift
index be9679e21f..877316dbc7 100644
--- a/submodules/WalletUI/Sources/WalletSplashScreen.swift
+++ b/submodules/WalletUI/Sources/WalletSplashScreen.swift
@@ -49,6 +49,7 @@ public final class WalletSplashScreen: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: defaultNavigationPresentationData.strings))
+ self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
@@ -287,14 +288,22 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor, additionalAttributes: [:])
let link = MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor, additionalAttributes: [NSAttributedString.Key.underlineStyle.rawValue: NSUnderlineStyle.single.rawValue as NSNumber])
termsText = parseMarkdownIntoAttributedString("By creating the wallet you accept\n[Terms of Conditions]().", attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center)
- self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/IntroIcon")
+ self.iconNode.image = nil
+ if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
+ self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, mode: .direct)
+ self.animationNode.visibility = true
+ }
secondaryActionText = ""
case .created:
title = "Congratulations"
text = NSAttributedString(string: "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down secret words and\nset up a secure passcode.", font: textFont, textColor: textColor)
buttonText = "Proceed"
termsText = NSAttributedString(string: "")
- self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/CreatedIcon")
+ self.iconNode.image = nil
+ if let path = getAppBundle().path(forResource: "WalletCreated", ofType: "tgs") {
+ self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, mode: .direct)
+ self.animationNode.visibility = true
+ }
secondaryActionText = ""
case .success:
title = "Ready to go!"
@@ -323,7 +332,11 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
text = NSAttributedString(string: "Please wait a few seconds for your transaction to be processed...", font: textFont, textColor: textColor)
buttonText = ""
termsText = NSAttributedString(string: "")
- self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/SendingIcon")
+ self.iconNode.image = nil
+ if let path = getAppBundle().path(forResource: "SendingGrams", ofType: "tgs") {
+ self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, mode: .direct)
+ self.animationNode.visibility = true
+ }
secondaryActionText = ""
case let .sent(_, amount):
title = "Done!"
@@ -392,7 +405,7 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode {
self.secondaryActionButtonNode = HighlightTrackingButtonNode()
- self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 10.0, gloss: true)
+ self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 10.0, gloss: false)
self.buttonNode.isHidden = buttonText.isEmpty
super.init()
diff --git a/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift b/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift
index 2973664876..d02d745564 100644
--- a/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift
+++ b/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift
@@ -80,7 +80,7 @@ private enum WalletTransactionInfoEntry: ItemListNodeEntry {
case let .infoHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .infoAddress(theme, text):
- return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], sectionId: self.section, style: .blocks)
+ return ItemListMultilineTextItem(theme: theme, text: text, enabledEntityTypes: [], font: .monospace, sectionId: self.section, style: .blocks)
case let .infoCopyAddress(theme, text):
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.copyWalletAddress()
@@ -346,20 +346,30 @@ private class WalletTransactionHeaderItemNode: ListViewItemNode {
let leftInset: CGFloat = 15.0 + params.leftInset
let verticalInset: CGFloat = 24.0
- let title: String
+ let balanceString: String
let titleColor: UIColor
let transferredValue = item.walletTransaction.transferredValue
if transferredValue <= 0 {
- title = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
+ balanceString = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.list.itemPrimaryTextColor
} else {
- title = "+\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
+ balanceString = "+\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
titleColor = item.theme.chatList.secretTitleColor
}
+ let title = NSMutableAttributedString()
+ if let range = balanceString.range(of: ".") {
+ let integralPart = String(balanceString[.. Void, secondaryAction: @escaping () -> Void) {
+ init(account: Account, presentationData: PresentationData, mode: WalletWordCheckMode, possibleWordList: [String], action: @escaping () -> Void, secondaryAction: @escaping () -> Void) {
self.presentationData = presentationData
self.mode = mode
self.action = action
@@ -2576,9 +2578,7 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
self.scrollNode = ASScrollNode()
self.scrollNode.canCancelAllTouchesInViews = true
- self.iconNode = ASImageNode()
- self.iconNode.displayWithoutProcessing = true
- self.iconNode.displaysAsynchronously = false
+ self.animationNode = AnimatedStickerNode()
let title: String
let text: NSAttributedString
@@ -2598,14 +2598,16 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
buttonText = "Continue"
secondaryActionText = ""
- self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/WordsCheckIcon")
+ if let path = getAppBundle().path(forResource: "WalletWordCheck", ofType: "tgs") {
+ self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, mode: .direct)
+ self.animationNode.visibility = true
+ }
case .import:
title = "24 Secret Words"
text = NSAttributedString(string: "Please restore access to your wallet by\nentering the 24 secret words you wrote down when creating the wallet.", font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
buttonText = "Continue"
secondaryActionText = "I don't have those"
wordIndices = Array(0 ..< 24)
- self.iconNode.image = nil
}
self.navigationTitleNode = ImmediateTextNode()
@@ -2660,7 +2662,7 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
self.addSubnode(self.scrollNode)
- self.scrollNode.addSubnode(self.iconNode)
+ self.scrollNode.addSubnode(self.animationNode)
self.scrollNode.addSubnode(self.titleNode)
self.scrollNode.addSubnode(self.textNode)
self.scrollNode.addSubnode(self.secondaryActionTitleNode)
@@ -2773,7 +2775,14 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: contentAreaSize))
- let iconSize = self.iconNode.image?.size ?? CGSize()
+ let iconSize: CGSize
+ switch self.mode {
+ case .import:
+ iconSize = CGSize()
+ case .verify:
+ iconSize = CGSize(width: 140.0, height: 140.0)
+ self.animationNode.updateLayout(size: iconSize)
+ }
let titleSize = self.titleNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
let navigationTitleSize = self.navigationTitleNode.updateLayout(CGSize(width: contentAreaSize.width - sideInset * 2.0, height: contentAreaSize.height))
@@ -2787,7 +2796,7 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
let contentVerticalOrigin = navigationHeight + 10.0
let iconFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize)
- transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
+ transition.updateFrame(node: self.animationNode, frame: iconFrame)
let titleFrame = CGRect(origin: CGPoint(x: floor((contentAreaSize.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((contentAreaSize.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
diff --git a/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift b/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift
index 91ddd75057..09ebdbea8c 100644
--- a/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift
+++ b/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift
@@ -10,6 +10,7 @@ import TelegramCore
import SolidRoundedButtonNode
import UndoUI
import AlertUI
+import AnimationUI
public final class WalletWordDisplayScreen: ViewController {
private let context: AccountContext
@@ -35,6 +36,7 @@ public final class WalletWordDisplayScreen: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: defaultNavigationPresentationData.strings))
+ self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
@@ -51,7 +53,7 @@ public final class WalletWordDisplayScreen: ViewController {
}
override public func loadDisplayNode() {
- self.displayNode = WalletWordDisplayScreenNode(presentationData: self.presentationData, wordList: self.wordList, action: { [weak self] in
+ self.displayNode = WalletWordDisplayScreenNode(account: self.context.account, presentationData: self.presentationData, wordList: self.wordList, action: { [weak self] in
guard let strongSelf = self else {
return
}
@@ -103,7 +105,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
private let navigationSeparatorNode: ASDisplayNode
private let navigationTitleNode: ImmediateTextNode
private let scrollNode: ASScrollNode
- private let iconNode: ASImageNode
+ private let animationNode: AnimatedStickerNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let wordNodes: [(ImmediateTextNode, ImmediateTextNode, ImmediateTextNode)]
@@ -111,7 +113,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
private var navigationHeight: CGFloat?
- init(presentationData: PresentationData, wordList: [String], action: @escaping () -> Void) {
+ init(account: Account, presentationData: PresentationData, wordList: [String], action: @escaping () -> Void) {
self.presentationData = presentationData
self.wordList = wordList
self.action = action
@@ -124,16 +126,16 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
self.scrollNode = ASScrollNode()
- self.iconNode = ASImageNode()
- self.iconNode.displayWithoutProcessing = true
- self.iconNode.displaysAsynchronously = false
+ self.animationNode = AnimatedStickerNode()
+ if let path = getAppBundle().path(forResource: "WalletWordList", ofType: "tgs") {
+ self.animationNode.setup(account: account, resource: .localFile(path), width: 280, height: 280, mode: .direct)
+ self.animationNode.visibility = true
+ }
let title: String = "24 Secret Words"
let text: String = "Write down these 24 words in the correct order and store them in a secret place.\n\nUse these secret words to restore access to your wallet if you lose your passcode or Telegram account."
let buttonText: String = "Done"
- self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/WordsDisplayIcon")
-
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
@@ -187,7 +189,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
self.addSubnode(self.scrollNode)
- self.scrollNode.addSubnode(self.iconNode)
+ self.scrollNode.addSubnode(self.animationNode)
self.scrollNode.addSubnode(self.titleNode)
self.scrollNode.addSubnode(self.textNode)
self.scrollNode.addSubnode(self.buttonNode)
@@ -257,7 +259,8 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
- let iconSize = self.iconNode.image?.size ?? CGSize(width: 50.0, height: 50.0)
+ let iconSize = CGSize(width: 140.0, height: 140.0)
+ self.animationNode.updateLayout(size: iconSize)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let navigationTitleSize = self.navigationTitleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
@@ -268,7 +271,7 @@ private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UISc
let contentVerticalOrigin = navigationHeight + 10.0
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize)
- transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
+ 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)
diff --git a/submodules/WalletUI/WalletUI.xcodeproj/project.pbxproj b/submodules/WalletUI/WalletUI.xcodeproj/project.pbxproj
index 66750110b2..05ecee3e32 100644
--- a/submodules/WalletUI/WalletUI.xcodeproj/project.pbxproj
+++ b/submodules/WalletUI/WalletUI.xcodeproj/project.pbxproj
@@ -931,6 +931,17 @@
sourceTree
SOURCE_ROOT
+ 1DD70E293117BD4900000000
+
+ isa
+ PBXFileReference
+ name
+ WalletRefreshNode.swift
+ path
+ Sources/WalletRefreshNode.swift
+ sourceTree
+ SOURCE_ROOT
+
1DD70E2948FA33F200000000
isa
@@ -1015,6 +1026,7 @@
1DD70E296D49CFFF00000000
1DD70E29DE28A96800000000
1DD70E2979DDEBBB00000000
+ 1DD70E293117BD4900000000
1DD70E2948FA33F200000000
1DD70E2986544B8D00000000
1DD70E2964068E1100000000
@@ -1109,6 +1121,13 @@
fileRef
1DD70E2979DDEBBB00000000
+ E7A30F043117BD4900000000
+
+ isa
+ PBXBuildFile
+ fileRef
+ 1DD70E293117BD4900000000
+
E7A30F0448FA33F200000000
isa
@@ -1165,6 +1184,7 @@
E7A30F046D49CFFF00000000
E7A30F04DE28A96800000000
E7A30F0479DDEBBB00000000
+ E7A30F043117BD4900000000
E7A30F0448FA33F200000000
E7A30F0486544B8D00000000
E7A30F0464068E1100000000
diff --git a/submodules/ton/tonlib-src/CMakeLists.txt b/submodules/ton/tonlib-src/CMakeLists.txt
index 7f01f988b1..a2bb0676cb 100644
--- a/submodules/ton/tonlib-src/CMakeLists.txt
+++ b/submodules/ton/tonlib-src/CMakeLists.txt
@@ -375,6 +375,10 @@ target_link_libraries(test-tonlib tdutils tdactor adnllite tl_api ton_crypto ton
add_executable(test-tonlib-offline test/test-td-main.cpp ${TONLIB_OFFLINE_TEST_SOURCE})
target_link_libraries(test-tonlib-offline tdutils tdactor adnllite tl_api ton_crypto ton_block fift-lib tl_tonlib_api tonlib)
+
+if (NOT CMAKE_CROSSCOMPILING)
+ add_dependencies(test-tonlib-offline gen_fif)
+endif()
#END tonlib
diff --git a/submodules/ton/tonlib-src/crypto/block/check-proof.cpp b/submodules/ton/tonlib-src/crypto/block/check-proof.cpp
index a856c876ee..c459b1b458 100644
--- a/submodules/ton/tonlib-src/crypto/block/check-proof.cpp
+++ b/submodules/ton/tonlib-src/crypto/block/check-proof.cpp
@@ -32,7 +32,7 @@ namespace block {
using namespace std::literals::string_literals;
td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blkid, ton::Bits256* store_shard_hash_to,
- bool check_state_hash) {
+ bool check_state_hash, td::uint32* save_utime) {
ton::RootHash vhash{root->get_hash().bits()};
if (vhash != blkid.root_hash) {
return td::Status::Error(PSTRING() << " block header for block " << blkid.to_str() << " has incorrect root hash "
@@ -47,6 +47,9 @@ td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blki
if (!(tlb::unpack_cell(root, blk) && tlb::unpack_cell(blk.info, info))) {
return td::Status::Error(std::string{"cannot unpack header for block "} + blkid.to_str());
}
+ if (save_utime) {
+ *save_utime = info.gen_utime;
+ }
if (store_shard_hash_to) {
vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update};
if (!(upd_cs.is_special() && upd_cs.prefetch_long(8) == 4 // merkle update
@@ -153,7 +156,8 @@ td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td:
}
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
- td::Ref root, ton::LogicalTime* last_trans_lt, ton::Bits256* last_trans_hash) {
+ td::Ref root, ton::LogicalTime* last_trans_lt, ton::Bits256* last_trans_hash,
+ td::uint32* save_utime) {
TRY_RESULT_PREFIX(Q_roots, vm::std_boc_deserialize_multi(std::move(proof)), "cannot deserialize account proof");
if (Q_roots.size() != 2) {
return td::Status::Error(PSLICE() << "account state proof must have exactly two roots");
@@ -169,9 +173,9 @@ td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const
return td::Status::Error("account state proof is invalid");
}
ton::Bits256 state_hash = state_root->get_hash().bits();
- TRY_STATUS_PREFIX(
- check_block_header_proof(vm::MerkleProof::virtualize(std::move(Q_roots[0]), 1), shard_blk, &state_hash, true),
- "error in account shard block header proof : ");
+ TRY_STATUS_PREFIX(check_block_header_proof(vm::MerkleProof::virtualize(std::move(Q_roots[0]), 1), shard_blk,
+ &state_hash, true, save_utime),
+ "error in account shard block header proof : ");
block::gen::ShardStateUnsplit::Record sstate;
if (!(tlb::unpack_cell(std::move(state_root), sstate))) {
return td::Status::Error("cannot unpack state header");
@@ -233,8 +237,8 @@ td::Result AccountState::validate(ton::BlockIdExt ref_blk, b
TRY_STATUS(block::check_shard_proof(blk, shard_blk, shard_proof.as_slice()));
Info res;
- TRY_STATUS(
- block::check_account_proof(proof.as_slice(), shard_blk, addr, root, &res.last_trans_lt, &res.last_trans_hash));
+ TRY_STATUS(block::check_account_proof(proof.as_slice(), shard_blk, addr, root, &res.last_trans_lt,
+ &res.last_trans_hash, &res.gen_utime));
res.root = std::move(root);
return res;
diff --git a/submodules/ton/tonlib-src/crypto/block/check-proof.h b/submodules/ton/tonlib-src/crypto/block/check-proof.h
index 664028155d..87d6b442b7 100644
--- a/submodules/ton/tonlib-src/crypto/block/check-proof.h
+++ b/submodules/ton/tonlib-src/crypto/block/check-proof.h
@@ -25,11 +25,12 @@ namespace block {
using td::Ref;
td::Status check_block_header_proof(td::Ref root, ton::BlockIdExt blkid,
- ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false);
+ ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false,
+ td::uint32* save_utime = nullptr);
td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::Slice shard_proof);
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
td::Ref root, ton::LogicalTime* last_trans_lt = nullptr,
- ton::Bits256* last_trans_hash = nullptr);
+ ton::Bits256* last_trans_hash = nullptr, td::uint32* save_utime = nullptr);
td::Result check_state_proof(ton::BlockIdExt blkid, td::Slice proof);
td::Result[> check_extract_state_proof(ton::BlockIdExt blkid, td::Slice proof, td::Slice data);
@@ -47,6 +48,7 @@ struct AccountState {
td::Ref root;
ton::LogicalTime last_trans_lt = 0;
ton::Bits256 last_trans_hash;
+ td::uint32 gen_utime{0};
};
td::Result validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const;
diff --git a/submodules/ton/tonlib-src/crypto/fift/IntCtx.h b/submodules/ton/tonlib-src/crypto/fift/IntCtx.h
index 753d2cb1d4..1adb8f325a 100644
--- a/submodules/ton/tonlib-src/crypto/fift/IntCtx.h
+++ b/submodules/ton/tonlib-src/crypto/fift/IntCtx.h
@@ -77,6 +77,7 @@ struct IntCtx {
vm::TonDb* ton_db{nullptr};
Dictionary* dictionary{nullptr};
SourceLookup* source_lookup{nullptr};
+ int* now{nullptr};
private:
std::string str;
diff --git a/submodules/ton/tonlib-src/crypto/fift/SourceLookup.h b/submodules/ton/tonlib-src/crypto/fift/SourceLookup.h
index 73d2cd918f..33ee71d4ef 100644
--- a/submodules/ton/tonlib-src/crypto/fift/SourceLookup.h
+++ b/submodules/ton/tonlib-src/crypto/fift/SourceLookup.h
@@ -20,6 +20,7 @@
#include
#include "td/utils/Status.h"
+#include "td/utils/Time.h"
namespace fift {
class FileLoader {
@@ -43,10 +44,21 @@ class OsFileLoader : public FileLoader {
bool is_file_exists(td::CSlice filename) override;
};
+class OsTime {
+ public:
+ virtual ~OsTime() = default;
+ virtual td::uint32 now() = 0;
+};
+
+//TODO: rename SourceLookup
class SourceLookup {
public:
SourceLookup() = default;
- explicit SourceLookup(std::unique_ptr file_loader) : file_loader_(std::move(file_loader)) {
+ explicit SourceLookup(std::unique_ptr file_loader, std::unique_ptr os_time = {})
+ : file_loader_(std::move(file_loader)), os_time_(std::move(os_time)) {
+ }
+ void set_os_time(std::unique_ptr os_time) {
+ os_time_ = std::move(os_time);
}
void add_include_path(td::string path);
td::Result lookup_source(std::string filename, std::string current_dir);
@@ -63,9 +75,16 @@ class SourceLookup {
bool is_file_exists(td::CSlice filename) {
return file_loader_->is_file_exists(filename);
}
+ td::uint32 now() {
+ if (os_time_) {
+ return os_time_->now();
+ }
+ return static_cast(td::Time::now());
+ }
protected:
std::unique_ptr file_loader_;
+ std::unique_ptr os_time_;
std::vector source_include_path_;
};
} // namespace fift
diff --git a/submodules/ton/tonlib-src/crypto/fift/utils.cpp b/submodules/ton/tonlib-src/crypto/fift/utils.cpp
index e5f9a597e5..1c16b2cd4a 100644
--- a/submodules/ton/tonlib-src/crypto/fift/utils.cpp
+++ b/submodules/ton/tonlib-src/crypto/fift/utils.cpp
@@ -163,13 +163,15 @@ td::Result create_mem_source_lookup(std::string main, std::s
return create_source_lookup(main, need_preamble, need_asm, need_ton_util, fift_dir);
}
-td::Result> compile_asm(td::Slice asm_code, std::string fift_dir) {
+td::Result> compile_asm(td::Slice asm_code, std::string fift_dir, bool is_raw) {
std::stringstream ss;
TRY_RESULT(source_lookup,
- create_source_lookup(PSTRING() << "\"Asm.fif\" include\n<{ " << asm_code << "\n}>c boc>B \"res\" B>file",
+ create_source_lookup(PSTRING() << "\"Asm.fif\" include\n " << (is_raw ? "<{" : "") << asm_code << "\n"
+ << (is_raw ? "}>c" : "") << " boc>B \"res\" B>file",
true, true, true, fift_dir));
TRY_RESULT(res, run_fift(std::move(source_lookup), &ss));
TRY_RESULT(boc, res.read_file("res"));
return vm::std_boc_deserialize(std::move(boc.data));
}
+
} // namespace fift
diff --git a/submodules/ton/tonlib-src/crypto/fift/utils.h b/submodules/ton/tonlib-src/crypto/fift/utils.h
index e6ed145287..e17eb8ae2c 100644
--- a/submodules/ton/tonlib-src/crypto/fift/utils.h
+++ b/submodules/ton/tonlib-src/crypto/fift/utils.h
@@ -31,5 +31,5 @@ td::Result create_mem_source_lookup(std::string main, std::s
bool need_ton_util = true);
td::Result mem_run_fift(std::string source, std::vector args = {}, std::string fift_dir = "");
td::Result mem_run_fift(SourceLookup source_lookup, std::vector args);
-td::Result> compile_asm(td::Slice asm_code, std::string fift_dir = "");
+td::Result> compile_asm(td::Slice asm_code, std::string fift_dir = "", bool is_raw = true);
} // namespace fift
diff --git a/submodules/ton/tonlib-src/crypto/fift/words.cpp b/submodules/ton/tonlib-src/crypto/fift/words.cpp
index 5c217fad44..01dcf802c8 100644
--- a/submodules/ton/tonlib-src/crypto/fift/words.cpp
+++ b/submodules/ton/tonlib-src/crypto/fift/words.cpp
@@ -1289,8 +1289,8 @@ void interpret_file_exists(IntCtx& ctx) {
// custom and crypto
-void interpret_now(vm::Stack& stack) {
- stack.push_smallint(std::time(nullptr));
+void interpret_now(IntCtx& ctx) {
+ ctx.stack.push_smallint(ctx.source_lookup->now());
}
void interpret_new_keypair(vm::Stack& stack) {
@@ -2534,7 +2534,7 @@ void init_words_common(Dictionary& d) {
d.def_ctx_word("B>file ", interpret_write_file);
d.def_ctx_word("file-exists? ", interpret_file_exists);
// custom & crypto
- d.def_stack_word("now ", interpret_now);
+ d.def_ctx_word("now ", interpret_now);
d.def_stack_word("newkeypair ", interpret_new_keypair);
d.def_stack_word("priv>pub ", interpret_priv_key_to_pub);
d.def_stack_word("ed25519_sign ", interpret_ed25519_sign);
diff --git a/submodules/ton/tonlib-src/crypto/smartcont/new-wallet-v2.fif b/submodules/ton/tonlib-src/crypto/smartcont/new-wallet-v2.fif
new file mode 100644
index 0000000000..eac43b168b
--- /dev/null
+++ b/submodules/ton/tonlib-src/crypto/smartcont/new-wallet-v2.fif
@@ -0,0 +1,62 @@
+#!/usr/bin/env fift -s
+"TonUtil.fif" include
+"Asm.fif" include
+
+{ ."usage: " @' $0 type ." []" cr
+ ."Creates a new advanced wallet in specified workchain, with private key saved to or loaded from .pk" cr
+ ."('new-wallet.pk' by default)" cr 1 halt
+} : usage
+$# 1- -2 and ' usage if
+
+$1 parse-workchain-id =: wc // set workchain id from command line argument
+def? $2 { @' $2 } { "new-wallet" } cond constant file-base
+
+."Creating new advanced wallet in workchain " wc . cr
+
+// Create new advanced wallet; code adapted from `wallet-code.fif`
+<{ SETCP0 DUP IFNOTRET // return if recv_internal
+ DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
+ DROP c4 PUSHCTR CTOS 32 PLDU // cnt
+ }>
+ INC 32 THROWIF // fail unless recv_external
+ 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs
+ SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs
+ c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key
+ s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno
+ EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno
+ s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash
+ s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key
+ ACCEPT
+ s0 s2 XCHG // public_key stored_seqno cs
+ WHILE:<{
+ DUP SREFS // public_key stored_seqno cs _40
+ }>DO<{ // public_key stored_seqno cs
+ // 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
+ 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode
+ SENDRAWMSG // public_key stored_seqno cs
+ }>
+ ENDS INC // public_key seqno'
+ NEWC 32 STU 256 STU ENDC c4 POP
+}>c // >libref
+// code
+ // data
+null // no libraries
+ // create StateInit
+dup ."StateInit: " ]
+dup ."signing message: "
+dup ."External message for initialization is " B dup Bx. cr
+file-base +"-query.boc" tuck B>file
+."(Saved wallet creating query to file " type .")" cr
diff --git a/submodules/ton/tonlib-src/crypto/smartcont/testgiver.fif b/submodules/ton/tonlib-src/crypto/smartcont/testgiver.fif
index bd9f8860c3..fe812487a5 100644
--- a/submodules/ton/tonlib-src/crypto/smartcont/testgiver.fif
+++ b/submodules/ton/tonlib-src/crypto/smartcont/testgiver.fif
@@ -24,7 +24,7 @@ dest_addr 2dup bounce 7 + .Addr ." = " .addr
// create a message (NB: 01b00.., b = bounce)
+ amount Gram, 0 9 64 32 + + 1+ 1+ u, 0 32 u, "GIFT" $, b>
dup ."enveloping message: " [-B ] []" cr
+ ."Creates a request to advanced wallet created by new-wallet-v2.fif, with private key loaded from file .pk "
+ ."and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" cr 1 halt
+} : usage
+def? $6 { @' $5 "-B" $= { @' $6 =: body-boc-file [forget] $6 def? $7 { @' $7 =: $5 [forget] $7 } { [forget] $5 } cond
+ @' $# 2- =: $# } if } if
+$# dup 4 < swap 5 > or ' usage if
+
+true constant bounce
+
+$1 =: file-base
+$2 bounce parse-load-address =: bounce 2=: dest_addr
+$3 parse-int =: seqno
+$4 $>GR =: amount
+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 +".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
+
+def? body-boc-file { @' body-boc-file file>B B>boc } { } cond
+constant body-cell
+
+."Transferring " amount .GR ."to account "
+dest_addr 2dup bounce 7 + .Addr ." = " .addr
+."seqno=0x" seqno x. ."bounce=" bounce . cr
+."Body of transfer message is " body-cell
+
+dup ."signing message: "
+dup ."resulting external message: " B dup Bx. cr
+savefile +".boc" tuck B>file
+."Query expires in " timeout . ."seconds" cr
+."(Saved to file " type .")" cr
diff --git a/submodules/ton/tonlib-src/crypto/smartcont/wallet.fif b/submodules/ton/tonlib-src/crypto/smartcont/wallet.fif
index 8742e3cc69..1159796399 100644
--- a/submodules/ton/tonlib-src/crypto/smartcont/wallet.fif
+++ b/submodules/ton/tonlib-src/crypto/smartcont/wallet.fif
@@ -5,9 +5,9 @@
."Creates a request to simple wallet created by new-wallet.fif, with private key loaded from file .pk "
."and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" cr 1 halt
} : usage
-$# dup 4 < swap 5 > or ' usage if
def? $6 { @' $5 "-B" $= { @' $6 =: body-boc-file [forget] $6 def? $7 { @' $7 =: $5 [forget] $7 } { [forget] $5 } cond
@' $# 2- =: $# } if } if
+$# dup 4 < swap 5 > or ' usage if
true constant bounce
@@ -16,13 +16,14 @@ $2 bounce parse-load-address =: bounce 2=: dest_addr
$3 parse-int =: seqno
$4 $>GR =: amount
def? $5 { @' $5 } { "wallet-query" } cond constant savefile
+3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
file-base +".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
-def? body-boc-file { @' body-boc-file file>B B>boc } { } cond
+def? body-boc-file { @' body-boc-file file>B B>boc } { } cond
constant body-cell
."Transferring " amount .GR ."to account "
@@ -34,7 +35,7 @@ dest_addr 2dup bounce 7 + .Addr ." = " .addr
-
+
dup ."signing message: " ");
+ store_field_end();
+ }
+
template
void store_field(const char *name, const T &value) {
store_field_begin(name);
@@ -240,6 +247,12 @@ class TlStorerToString {
store_field_end();
}
+ void store_bytes_field(const char *name, const SecureString &value) {
+ store_field_begin(name);
+ result.append("");
+ store_field_end();
+ }
+
template
void store_bytes_field(const char *name, const BytesT &value) {
static const char *hex = "0123456789ABCDEF";
diff --git a/submodules/ton/tonlib-src/tl/generate/auto/tl/ton_api.cpp b/submodules/ton/tonlib-src/tl/generate/auto/tl/ton_api.cpp
index bfbdee4ed8..c6c6f32804 100644
--- a/submodules/ton/tonlib-src/tl/generate/auto/tl/ton_api.cpp
+++ b/submodules/ton/tonlib-src/tl/generate/auto/tl/ton_api.cpp
@@ -446,6 +446,8 @@ object_ptr