mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Wallet improvements
This commit is contained in:
@@ -11,6 +11,59 @@ 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
|
||||
@@ -20,6 +73,11 @@ public final class WalletInfoScreen: ViewController {
|
||||
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, address: String) {
|
||||
self.context = context
|
||||
self.tonContext = tonContext
|
||||
@@ -77,6 +135,8 @@ public final class WalletInfoScreen: ViewController {
|
||||
})
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self._ready.set((self.displayNode as! WalletInfoScreenNode).contentReady.get())
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
@@ -126,11 +186,7 @@ private final class WalletInfoBalanceNode: ASDisplayNode {
|
||||
|
||||
let balanceTextFrame = CGRect(origin: balanceOrigin, size: balanceTextSize)
|
||||
let balanceIconFrame: CGRect
|
||||
if self.isLoading {
|
||||
balanceIconFrame = CGRect(origin: CGPoint(x: floor((width - balanceIconSize.width) / 2.0), y: balanceTextFrame.minY + floor((balanceTextFrame.height - balanceIconSize.height) / 2.0)), size: balanceIconSize)
|
||||
} else {
|
||||
balanceIconFrame = CGRect(origin: CGPoint(x: balanceTextFrame.maxX + balanceIconSpacing, y: balanceTextFrame.minY + floor((balanceTextFrame.height - balanceIconSize.height) / 2.0)), size: balanceIconSize)
|
||||
}
|
||||
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)
|
||||
transition.updateFrame(node: self.balanceIconNode, frame: balanceIconFrame)
|
||||
|
||||
@@ -140,20 +196,33 @@ private final class WalletInfoBalanceNode: ASDisplayNode {
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
let balanceNode: WalletInfoBalanceNode
|
||||
private let refreshNode: AnimatedStickerNode
|
||||
private let balanceSubtitleNode: ImmediateTextNode
|
||||
private let balanceTimestampNode: ImmediateTextNode
|
||||
private let receiveButtonNode: SolidRoundedButtonNode
|
||||
private let sendButtonNode: SolidRoundedButtonNode
|
||||
private let headerBackgroundNode: ASImageNode
|
||||
|
||||
init(theme: PresentationTheme, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
|
||||
init(account: Account, theme: PresentationTheme, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void) {
|
||||
self.balanceNode = WalletInfoBalanceNode(theme: 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
|
||||
@@ -168,6 +237,13 @@ 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)
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.headerBackgroundNode)
|
||||
@@ -175,6 +251,8 @@ 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 = {
|
||||
receiveAction()
|
||||
@@ -206,11 +284,13 @@ 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 balanceSize = CGSize(width: size.width, height: balanceHeight)
|
||||
|
||||
let minHeaderScale: CGFloat = 0.435
|
||||
let maxHeaderScale: CGFloat = min(1.0, (size.width - 40.0) / balanceSize.width)
|
||||
let minHeaderScale: CGFloat = min(0.435, (size.width - 80.0 * 2.0) / balanceSize.width)
|
||||
|
||||
let minHeaderHeight: CGFloat = balanceSize.height + balanceSubtitleSize.height + balanceSubtitleSpacing
|
||||
|
||||
@@ -219,13 +299,39 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
||||
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 * 1.0 + (1.0 - headerScaleTransition) * minHeaderScale
|
||||
let headerScale = headerScaleTransition * maxHeaderScale + (1.0 - headerScaleTransition) * minHeaderScale
|
||||
|
||||
let refreshSize = CGSize(width: 32.0, height: 32.0)
|
||||
self.refreshNode.updateLayout(size: refreshSize)
|
||||
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))
|
||||
if self.balance == nil {
|
||||
transition.updateAlpha(node: self.refreshNode, alpha: 0.0)
|
||||
transition.updateSublayerTransformScale(node: self.refreshNode, scale: 0.1)
|
||||
self.refreshNode.visibility = false
|
||||
} else if self.isRefreshing {
|
||||
transition.updateAlpha(node: self.refreshNode, alpha: 1.0)
|
||||
transition.updateSublayerTransformScale(node: self.refreshNode, scale: 1.0)
|
||||
self.refreshNode.visibility = true
|
||||
} 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 balanceFrame = CGRect(origin: CGPoint(x: 0.0, y: headerY), size: balanceSize)
|
||||
transition.updateFrame(node: self.balanceNode, frame: balanceFrame)
|
||||
transition.updateSublayerTransformScale(node: self.balanceNode, scale: headerScale)
|
||||
|
||||
transition.updateFrameAdditive(node: self.balanceSubtitleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - balanceSubtitleSize.width) / 2.0), y: balanceFrame.midY + (balanceFrame.height / 2.0 * headerScale) + balanceSubtitleSpacing), size: balanceSubtitleSize))
|
||||
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)))
|
||||
@@ -252,11 +358,13 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if self.balance == nil {
|
||||
self.balanceNode.balanceTextNode.isHidden = true
|
||||
self.balanceNode.isHidden = true
|
||||
self.balanceSubtitleNode.isHidden = true
|
||||
self.balanceTimestampNode.isHidden = true
|
||||
} else {
|
||||
self.balanceNode.balanceTextNode.isHidden = false
|
||||
self.balanceNode.isHidden = false
|
||||
self.balanceSubtitleNode.isHidden = false
|
||||
self.balanceTimestampNode.isHidden = false
|
||||
}
|
||||
transition.updateFrame(node: self.receiveButtonNode, frame: receiveButtonFrame)
|
||||
transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha)
|
||||
@@ -276,12 +384,19 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.sendButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.receiveButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
func becameReady(animated: Bool) {
|
||||
if animated {
|
||||
self.sendButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
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.balanceNode.isLoading = false
|
||||
self.balanceNode.balanceTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.balanceSubtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,24 +473,34 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
private let address: String
|
||||
private let openTransaction: (WalletTransaction) -> Void
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private let headerNode: WalletInfoHeaderNode
|
||||
private let listNode: ListView
|
||||
private let loadingIndicator: UIActivityIndicatorView
|
||||
|
||||
private var enqueuedTransactions: [WalletInfoListTransaction] = []
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let balanceDisposable = MetaDisposable()
|
||||
private let stateDisposable = MetaDisposable()
|
||||
private let transactionListDisposable = MetaDisposable()
|
||||
|
||||
private var listOffset: CGFloat?
|
||||
private var reloadingState: Bool = false
|
||||
private var loadingMoreTransactions: Bool = false
|
||||
private var canLoadMoreTransactions: Bool = true
|
||||
|
||||
private var combinedState: CombinedWalletState?
|
||||
private var currentEntries: [WalletInfoListEntry]?
|
||||
|
||||
private var isReady: Bool = false
|
||||
|
||||
let contentReady = Promise<Bool>()
|
||||
private var didSetContentReady = false
|
||||
|
||||
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) {
|
||||
self.account = account
|
||||
self.tonContext = tonContext
|
||||
@@ -384,47 +509,50 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
self.address = address
|
||||
self.openTransaction = openTransaction
|
||||
|
||||
self.headerNode = WalletInfoHeaderNode(theme: presentationData.theme, sendAction: sendAction, receiveAction: receiveAction)
|
||||
self.headerNode = WalletInfoHeaderNode(account: account, theme: presentationData.theme, sendAction: sendAction, receiveAction: receiveAction)
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
|
||||
self.listNode.isHidden = true
|
||||
|
||||
self.loadingIndicator = UIActivityIndicatorView(style: .whiteLarge)
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = .white
|
||||
|
||||
self.balanceDisposable.set((getWalletState(address: address, tonInstance: tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.headerNode.balanceNode.balance = formatBalanceText(max(0, value.balance))
|
||||
strongSelf.headerNode.balance = max(0, value.balance)
|
||||
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
|
||||
let wasReady = strongSelf.isReady
|
||||
strongSelf.isReady = strongSelf.headerNode.balance != nil && strongSelf.currentEntries != nil
|
||||
if strongSelf.isReady && !wasReady {
|
||||
strongSelf.animateReadyIn()
|
||||
}
|
||||
}))
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
self.addSubnode(self.headerNode)
|
||||
self.view.addSubview(self.loadingIndicator)
|
||||
|
||||
var canBeginRefresh = true
|
||||
var isScrolling = false
|
||||
self.listNode.beganInteractiveDragging = {
|
||||
isScrolling = true
|
||||
}
|
||||
self.listNode.endedInteractiveDragging = {
|
||||
isScrolling = false
|
||||
}
|
||||
|
||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
|
||||
guard let strongSelf = self, let (layout, navigationHeight) = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
let headerHeight: CGFloat = navigationHeight + 260.0
|
||||
strongSelf.listOffset = offset
|
||||
|
||||
if strongSelf.isReady {
|
||||
if !strongSelf.reloadingState && canBeginRefresh && isScrolling {
|
||||
if offset >= headerHeight + 100.0 {
|
||||
canBeginRefresh = false
|
||||
strongSelf.headerNode.isRefreshing = true
|
||||
strongSelf.headerNode.animateBeganRefreshing()
|
||||
strongSelf.hapticFeedback.impact()
|
||||
strongSelf.refreshTransactions()
|
||||
}
|
||||
}
|
||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: offset, transition: listTransition)
|
||||
}
|
||||
}
|
||||
@@ -436,12 +564,14 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
guard case let .known(value) = offset, value < 100.0 else {
|
||||
return
|
||||
}
|
||||
if !strongSelf.loadingMoreTransactions && strongSelf.canLoadMoreTransactions {
|
||||
if !strongSelf.loadingMoreTransactions && !strongSelf.reloadingState && strongSelf.canLoadMoreTransactions {
|
||||
strongSelf.loadMoreTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.didEndScrolling = { [weak self] in
|
||||
canBeginRefresh = true
|
||||
|
||||
guard let strongSelf = self, let (_, navigationHeight) = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
@@ -460,6 +590,26 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
self.refreshTransactions()
|
||||
|
||||
self.updateTimestampTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
|
||||
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)
|
||||
}
|
||||
}
|
||||
}, queue: .mainQueue())
|
||||
self.updateTimestampTimer?.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stateDisposable.dispose()
|
||||
self.transactionListDisposable.dispose()
|
||||
self.updateTimestampTimer?.invalidate()
|
||||
}
|
||||
|
||||
func scrollToHideHeader() {
|
||||
@@ -477,6 +627,9 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
let isFirstLayout = self.validLayout == nil
|
||||
self.validLayout = (layout, navigationHeight)
|
||||
|
||||
let indicatorSize = self.loadingIndicator.bounds.size
|
||||
self.loadingIndicator.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: floor((layout.size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
|
||||
|
||||
let headerHeight: CGFloat = navigationHeight + 260.0
|
||||
let topInset: CGFloat = headerHeight
|
||||
|
||||
@@ -531,13 +684,73 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
private func refreshTransactions() {
|
||||
self.transactionListDisposable.set(nil)
|
||||
self.loadingMoreTransactions = true
|
||||
self.reloadingState = true
|
||||
|
||||
self.transactionListDisposable.set((getWalletTransactions(address: self.address, previousId: nil, tonInstance: self.tonContext.instance)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transactions in
|
||||
self.headerNode.timestampString = "updating"
|
||||
|
||||
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
|
||||
}
|
||||
strongSelf.transactionsLoaded(isReload: true, transactions: transactions)
|
||||
let combinedState: CombinedWalletState?
|
||||
switch value {
|
||||
case let .cached(state):
|
||||
if state == nil {
|
||||
strongSelf.loadingIndicator.startAnimating()
|
||||
} else {
|
||||
strongSelf.loadingIndicator.stopAnimating()
|
||||
strongSelf.loadingIndicator.isHidden = true
|
||||
}
|
||||
combinedState = state
|
||||
case let .updated(state):
|
||||
strongSelf.loadingIndicator.stopAnimating()
|
||||
strongSelf.loadingIndicator.isHidden = true
|
||||
combinedState = state
|
||||
}
|
||||
|
||||
strongSelf.combinedState = combinedState
|
||||
if let combinedState = combinedState {
|
||||
strongSelf.headerNode.balanceNode.balance = formatBalanceText(max(0, combinedState.walletState.balance))
|
||||
strongSelf.headerNode.balance = max(0, combinedState.walletState.balance)
|
||||
|
||||
if strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
|
||||
strongSelf.reloadingState = false
|
||||
|
||||
strongSelf.headerNode.timestampString = lastUpdateTimestampString(strings: strongSelf.presentationData.strings, dateTimeFormat: strongSelf.presentationData.dateTimeFormat, statusTimestamp: Int32(clamping: combinedState.timestamp), relativeTo: Int32(Date().timeIntervalSince1970))
|
||||
|
||||
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.transactionsLoaded(isReload: true, transactions: combinedState.topTransactions)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
let wasReady = strongSelf.isReady
|
||||
strongSelf.isReady = strongSelf.combinedState != nil
|
||||
|
||||
if strongSelf.isReady && !wasReady {
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.headerNode.update(size: strongSelf.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate)
|
||||
}
|
||||
|
||||
strongSelf.becameReady(animated: strongSelf.didSetContentReady)
|
||||
}
|
||||
}
|
||||
|
||||
if !strongSelf.didSetContentReady {
|
||||
strongSelf.didSetContentReady = true
|
||||
strongSelf.contentReady.set(.single(true))
|
||||
}
|
||||
}, error: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@@ -576,12 +789,14 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
self.loadingMoreTransactions = false
|
||||
self.canLoadMoreTransactions = transactions.count > 2
|
||||
|
||||
let isFirst = self.currentEntries == nil
|
||||
|
||||
var updatedEntries: [WalletInfoListEntry] = []
|
||||
if isReload {
|
||||
var existingIds = Set<WalletTransactionId>()
|
||||
for transaction in transactions {
|
||||
updatedEntries.append(.transaction(updatedEntries.count, transaction))
|
||||
if !existingIds.contains(transaction.transactionId) {
|
||||
existingIds.insert(transaction.transactionId)
|
||||
updatedEntries.append(.transaction(updatedEntries.count, transaction))
|
||||
}
|
||||
}
|
||||
if updatedEntries.isEmpty {
|
||||
updatedEntries.append(.empty(self.address))
|
||||
@@ -625,13 +840,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
|
||||
self.enqueuedTransactions.append(transaction)
|
||||
self.dequeueTransaction()
|
||||
|
||||
let wasReady = self.isReady
|
||||
self.isReady = self.headerNode.balance != nil && self.currentEntries != nil
|
||||
|
||||
if self.isReady && !wasReady {
|
||||
self.animateReadyIn()
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransaction() {
|
||||
@@ -650,11 +858,13 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
||||
})
|
||||
}
|
||||
|
||||
private func animateReadyIn() {
|
||||
private func becameReady(animated: Bool) {
|
||||
self.listNode.isHidden = false
|
||||
self.headerNode.animateIn()
|
||||
self.loadingIndicator.stopAnimating()
|
||||
self.loadingIndicator.isHidden = true
|
||||
self.headerNode.becameReady(animated: animated)
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.5, curve: .spring))
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user