Update Wallet

This commit is contained in:
Peter 2019-10-22 22:34:12 +04:00
parent b9fa37c984
commit 985e8d26a0
9 changed files with 451 additions and 22 deletions

View File

@ -79,8 +79,6 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>

View File

@ -1,17 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11163.2" systemVersion="16A239j" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11133"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="O8c-13-3vw">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="deK-KQ-TX4">
<rect key="frame" x="0.0" y="0.0" width="414" height="260"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" translucent="NO" prompted="NO"/>
<point key="canvasLocation" x="136.23188405797103" y="101.11607142857143"/>
</view>
</objects>
</document>

View File

@ -93,6 +93,13 @@ final class WalletStorageInterfaceImpl: WalletStorageInterface {
return updatedRecords
}
}
func customWalletConfiguration() -> Signal<CustomWalletConfiguration?, NoError> {
return .single(nil)
}
func updateCustomWalletConfiguration(_ value: CustomWalletConfiguration?) {
}
}
final class WalletContextImpl: WalletContext {
@ -103,6 +110,8 @@ final class WalletContextImpl: WalletContext {
let keychain: TonKeychain
let presentationData: WalletPresentationData
let supportsCustomConfigurations: Bool = false
var inForeground: Signal<Bool, NoError> {
return self.context.sharedContext.applicationBindings.applicationInForeground
}

View File

@ -335,8 +335,8 @@ typedef enum {
return;
} else if (response.object->get_id() == tonlib_api::updateSyncState::ID) {
auto result = tonlib_api::move_object_as<tonlib_api::updateSyncState>(response.object);
if (result.sync_state_->get_id() == tonlib_api::syncStateInProgress::ID) {
auto syncStateInProgress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(result.sync_state_);
if (result->sync_state_->get_id() == tonlib_api::syncStateInProgress::ID) {
auto syncStateInProgress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(result->sync_state_);
int32_t currentDelta = syncStateInProgress->current_seqno_ - syncStateInProgress->from_seqno_;
int32_t fullDelta = syncStateInProgress->to_seqno_ - syncStateInProgress->from_seqno_;
if (currentDelta > 0 && fullDelta > 0) {
@ -345,10 +345,10 @@ typedef enum {
} else {
syncStateUpdated(0.0f);
}
} else if (result.sync_state_->get_id() == tonlib_api::syncStateDone::ID) {
} else if (result->sync_state_->get_id() == tonlib_api::syncStateDone::ID) {
syncStateUpdated(1.0f);
}
return
return;
}
NSNumber *requestId = @(response.id);
[requestHandlersLock lock];
@ -425,12 +425,17 @@ typedef enum {
}
}];
bool ignoreCache = false;
#if DEBUG
ignoreCache = true;
#endif
auto query = make_object<tonlib_api::init>(make_object<tonlib_api::options>(
make_object<tonlib_api::config>(
configString.UTF8String,
blockchainName.UTF8String,
enableExternalRequests,
false
ignoreCache
),
make_object<tonlib_api::keyStoreTypeDirectory>(
keystoreDirectory.UTF8String

View File

@ -55,7 +55,7 @@ private final class TonInstanceImpl {
private let blockchainName: String
private let proxy: TonNetworkProxy?
private var instance: TON?
private let syncStateProgress = ValuePromise<Float>(0.0)
fileprivate let syncStateProgress = ValuePromise<Float>(0.0)
init(queue: Queue, basePath: String, config: String, blockchainName: String, proxy: TonNetworkProxy?) {
self.queue = queue
@ -98,6 +98,18 @@ public final class TonInstance {
private let queue: Queue
private let impl: QueueLocalObject<TonInstanceImpl>
public var syncProgress: Signal<Float, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.syncStateProgress.get().start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public init(basePath: String, config: String, blockchainName: String, proxy: TonNetworkProxy?) {
self.queue = .mainQueue()
let queue = self.queue

View File

@ -0,0 +1,316 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
public func generateItemListCheckIcon(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setLineWidth(1.98)
context.setLineCap(.round)
context.setLineJoin(.round)
context.translateBy(x: 1.0, y: 1.0)
let _ = try? drawSvgPath(context, path: "M0.215053763,4.36080467 L3.31621263,7.70466293 L3.31621263,7.70466293 C3.35339229,7.74475231 3.41603123,7.74711109 3.45612061,7.70993143 C3.45920681,7.70706923 3.46210733,7.70401312 3.46480451,7.70078171 L9.89247312,0 S ")
})
}
public enum ItemListCheckboxItemStyle {
case left
case right
}
public enum ItemListCheckboxItemColor {
case accent
}
class ItemListCheckboxItem: ListViewItem, ItemListItem {
let theme: WalletTheme
let title: String
let style: ItemListCheckboxItemStyle
let color: ItemListCheckboxItemColor
let checked: Bool
let zeroSeparatorInsets: Bool
let sectionId: ItemListSectionId
let action: () -> Void
init(theme: WalletTheme, title: String, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, checked: Bool, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) {
self.theme = theme
self.title = title
self.style = style
self.color = color
self.checked = checked
self.zeroSeparatorInsets = zeroSeparatorInsets
self.sectionId = sectionId
self.action = action
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ItemListCheckboxItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? ItemListCheckboxItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
public var selectable: Bool = true
public func selected(listView: ListView){
listView.clearHighlightAnimated(true)
self.action()
}
}
private let titleFont = Font.regular(17.0)
public class ItemListCheckboxItemNode: ListViewItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
private let activateArea: AccessibilityAreaNode
private let iconNode: ASImageNode
private let titleNode: TextNode
private var item: ItemListCheckboxItem?
public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.maskNode = ASImageNode()
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.iconNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.activateArea)
self.activateArea.activate = { [weak self] in
self?.item?.action()
return true
}
}
func asyncLayout() -> (_ item: ItemListCheckboxItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let currentItem = self.item
return { item, params, neighbors in
var leftInset: CGFloat = params.leftInset
switch item.style {
case .left:
leftInset += 44.0
case .right:
leftInset += 16.0
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel
let insets = itemListNeighborsGroupedInsets(neighbors)
let contentSize = CGSize(width: params.width, height: 44.0)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
var updateCheckImage: UIImage?
var updatedTheme: WalletTheme?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
}
if currentItem?.theme !== item.theme || currentItem?.color != item.color {
switch item.color {
case .accent:
updateCheckImage = generateItemListCheckIcon(color: item.theme.list.itemAccentColor)
}
}
return (layout, { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.activateArea.accessibilityLabel = item.title
if item.checked {
strongSelf.activateArea.accessibilityValue = "Selected"
} else {
strongSelf.activateArea.accessibilityValue = ""
}
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
if let updateCheckImage = updateCheckImage {
strongSelf.iconNode.image = updateCheckImage
}
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
}
let _ = titleApply()
if let image = strongSelf.iconNode.image {
switch item.style {
case .left:
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
case .right:
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - image.size.width - floor((44.0 - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
}
}
strongSelf.iconNode.isHidden = !item.checked
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
if item.zeroSeparatorInsets {
bottomStripeInset = 0.0
} else {
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
}
strongSelf.maskNode.image = hasCorners ? cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
}
})
}
}
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
var anchorNode: ASDisplayNode?
if self.bottomStripeNode.supernode != nil {
anchorNode = self.bottomStripeNode
} else if self.topStripeNode.supernode != nil {
anchorNode = self.topStripeNode
} else if self.backgroundNode.supernode != nil {
anchorNode = self.backgroundNode
}
if let anchorNode = anchorNode {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
} else {
self.addSubnode(self.highlightedBackgroundNode)
}
}
} else {
if self.highlightedBackgroundNode.supernode != nil {
if animated {
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
if let strongSelf = self {
if completed {
strongSelf.highlightedBackgroundNode.removeFromSupernode()
}
}
})
self.highlightedBackgroundNode.alpha = 0.0
} else {
self.highlightedBackgroundNode.removeFromSupernode()
}
}
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -10,14 +10,22 @@ import WalletCore
private final class WalletConfigurationScreenArguments {
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void
let dismissInput: () -> Void
let updateSelectedMode: (WalletConfigurationScreenMode) -> Void
init(updateState: @escaping ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void, dismissInput: @escaping () -> Void) {
init(updateState: @escaping ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void, dismissInput: @escaping () -> Void, updateSelectedMode: @escaping (WalletConfigurationScreenMode) -> Void) {
self.updateState = updateState
self.dismissInput = dismissInput
self.updateSelectedMode = updateSelectedMode
}
}
private enum WalletConfigurationScreenMode {
case `default`
case customString
}
private enum WalletConfigurationScreenSection: Int32 {
case mode
case configString
}
@ -34,10 +42,14 @@ private enum WalletConfigurationScreenEntryTag: ItemListItemTag {
}
private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
case modeDefault(WalletTheme, String, Bool)
case modeCustomString(WalletTheme, String, Bool)
case configString(WalletTheme, String, String)
var section: ItemListSectionId {
switch self {
case .modeDefault, .modeCustomString:
return WalletConfigurationScreenSection.mode.rawValue
case .configString:
return WalletConfigurationScreenSection.configString.rawValue
}
@ -45,8 +57,12 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
var stableId: Int32 {
switch self {
case .configString:
case .modeDefault:
return 0
case .modeCustomString:
return 1
case .configString:
return 2
}
}
@ -57,6 +73,14 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
func item(_ arguments: Any) -> ListViewItem {
let arguments = arguments as! WalletConfigurationScreenArguments
switch self {
case let .modeDefault(theme, text, isSelected):
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateSelectedMode(.default)
})
case let .modeCustomString(theme, text, isSelected):
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateSelectedMode(.customString)
})
case let .configString(theme, placeholder, text):
return ItemListMultilineInputItem(theme: theme, text: text, placeholder: placeholder, maxLength: nil, sectionId: self.section, style: .blocks, capitalization: false, autocorrection: false, returnKeyType: .done, minimalHeight: nil, textUpdated: { value in
arguments.updateState { state in
@ -72,17 +96,31 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
}
private struct WalletConfigurationScreenState: Equatable {
var mode: WalletConfigurationScreenMode
var configString: String
var isEmpty: Bool {
return self.configString.isEmpty
switch self.mode {
case .default:
return false
case .customString:
return self.configString.isEmpty
}
}
}
private func walletConfigurationScreenEntries(presentationData: WalletPresentationData, state: WalletConfigurationScreenState) -> [WalletConfigurationScreenEntry] {
var entries: [WalletConfigurationScreenEntry] = []
entries.append(.modeDefault(presentationData.theme, "Default", state.mode == .default))
entries.append(.modeCustomString(presentationData.theme, "Custom", state.mode == .customString))
entries.append(.configString(presentationData.theme, "", state.configString))
switch state.mode {
case .default:
break
case .customString:
entries.append(.configString(presentationData.theme, "", state.configString))
}
return entries
}
@ -104,7 +142,7 @@ func walletConfigurationScreen(context: WalletContext, currentConfiguration: Cus
configString = string
}
}
let initialState = WalletConfigurationScreenState(configString: configString)
let initialState = WalletConfigurationScreenState(mode: currentConfiguration == nil ? .default : .customString, configString: configString)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void = { f in
@ -122,17 +160,31 @@ func walletConfigurationScreen(context: WalletContext, currentConfiguration: Cus
updateState(f)
}, dismissInput: {
dismissInputImpl?()
}, updateSelectedMode: { mode in
updateState { state in
var state = state
state.mode = mode
return state
}
})
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Navigation_Cancel), style: .regular, enabled: true, action: {
dismissImpl?()
})
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Configuration_Apply), style: .bold, enabled: !state.isEmpty, action: {
let state = stateValue.with { $0 }
let configuration: CustomWalletConfiguration?
if state.configString.isEmpty {
switch state.mode {
case .default:
configuration = nil
} else {
configuration = .string(state.configString)
case .customString:
if state.configString.isEmpty {
configuration = nil
} else {
configuration = .string(state.configString)
}
}
context.storage.updateCustomWalletConfiguration(configuration)
dismissImpl?()
@ -145,6 +197,7 @@ func walletConfigurationScreen(context: WalletContext, currentConfiguration: Cus
}
let controller = WalletConfigurationScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil)
controller.navigationPresentation = .modal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
controller.experimentalSnapScrollToItem = true
controller.didAppear = { _ in

View File

@ -233,7 +233,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
private let hasActions: Bool
let balanceNode: WalletInfoBalanceNode
private let refreshNode: WalletRefreshNode
let refreshNode: WalletRefreshNode
private let balanceSubtitleNode: ImmediateTextNode
private let receiveButtonNode: SolidRoundedButtonNode
private let receiveGramsButtonNode: SolidRoundedButtonNode
@ -552,6 +552,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private var pollCombinedStateDisposable: Disposable?
private var watchCombinedStateDisposable: Disposable?
private var refreshProgressDisposable: Disposable?
init(context: WalletContext, presentationData: WalletPresentationData, walletInfo: WalletInfo?, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletInfoTransaction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
@ -690,6 +691,17 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.pollCombinedStateDisposable = (pollCombinedState
|> deliverOnMainQueue).start()
self.refreshProgressDisposable = (context.tonInstance.syncProgress
|> deliverOnMainQueue).start(next: { [weak self] progress in
guard let strongSelf = self else {
return
}
strongSelf.headerNode.refreshNode.refreshProgress = progress
if strongSelf.headerNode.isRefreshing, strongSelf.isReady, let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.headerNode.refreshNode.update(state: .refreshing)
}
})
}
deinit {
@ -698,6 +710,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.updateTimestampTimer?.invalidate()
self.pollCombinedStateDisposable?.dispose()
self.watchCombinedStateDisposable?.dispose()
self.refreshProgressDisposable?.dispose()
}
func scrollToHideHeader() {
@ -775,6 +788,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
self.reloadingState = true
self.headerNode.isRefreshing = true
self.headerNode.refreshNode.refreshProgress = 0.0
let subject: CombinedWalletStateSubject
if let walletInfo = self.walletInfo {

View File

@ -123,6 +123,8 @@ final class WalletRefreshNode: ASDisplayNode {
private var state: WalletRefreshState?
var refreshProgress: Float = 0.0
private let animator: ConstantDisplayLinkAnimator
init(strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat) {
@ -212,10 +214,15 @@ final class WalletRefreshNode: ASDisplayNode {
}
}
private var cachedProgress: Float = 0.0
func update(state: WalletRefreshState) {
if self.state == state {
if self.state == state && self.cachedProgress == self.refreshProgress {
return
}
let ignoreProgressValue = self.refreshProgress == 0.0 || (self.cachedProgress == 0.0 && self.refreshProgress == 1.0)
self.cachedProgress = self.refreshProgress
let previousState = self.state
self.state = state
@ -227,7 +234,12 @@ final class WalletRefreshNode: ASDisplayNode {
title = lastUpdateTimestampString(strings: self.strings, dateTimeFormat: dateTimeFormat, statusTimestamp: ts, relativeTo: Int32(Date().timeIntervalSince1970))
pullProgress = progress
case .refreshing:
title = self.strings.Wallet_Info_Updating
if ignoreProgressValue {
title = self.strings.Wallet_Info_Updating
} else {
let percent = Int(self.refreshProgress * 100.0)
title = self.strings.Wallet_Info_Updating + " \(percent)%"
}
}
if let previousState = previousState {