mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Update Wallet
This commit is contained in:
parent
b9fa37c984
commit
985e8d26a0
@ -79,8 +79,6 @@
|
|||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<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"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
<view contentMode="scaleToFill" id="O8c-13-3vw">
|
<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"/>
|
<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"/>
|
<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>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
</document>
|
</document>
|
||||||
|
@ -93,6 +93,13 @@ final class WalletStorageInterfaceImpl: WalletStorageInterface {
|
|||||||
return updatedRecords
|
return updatedRecords
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func customWalletConfiguration() -> Signal<CustomWalletConfiguration?, NoError> {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCustomWalletConfiguration(_ value: CustomWalletConfiguration?) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class WalletContextImpl: WalletContext {
|
final class WalletContextImpl: WalletContext {
|
||||||
@ -103,6 +110,8 @@ final class WalletContextImpl: WalletContext {
|
|||||||
let keychain: TonKeychain
|
let keychain: TonKeychain
|
||||||
let presentationData: WalletPresentationData
|
let presentationData: WalletPresentationData
|
||||||
|
|
||||||
|
let supportsCustomConfigurations: Bool = false
|
||||||
|
|
||||||
var inForeground: Signal<Bool, NoError> {
|
var inForeground: Signal<Bool, NoError> {
|
||||||
return self.context.sharedContext.applicationBindings.applicationInForeground
|
return self.context.sharedContext.applicationBindings.applicationInForeground
|
||||||
}
|
}
|
||||||
|
@ -335,8 +335,8 @@ typedef enum {
|
|||||||
return;
|
return;
|
||||||
} else if (response.object->get_id() == tonlib_api::updateSyncState::ID) {
|
} else if (response.object->get_id() == tonlib_api::updateSyncState::ID) {
|
||||||
auto result = tonlib_api::move_object_as<tonlib_api::updateSyncState>(response.object);
|
auto result = tonlib_api::move_object_as<tonlib_api::updateSyncState>(response.object);
|
||||||
if (result.sync_state_->get_id() == tonlib_api::syncStateInProgress::ID) {
|
if (result->sync_state_->get_id() == tonlib_api::syncStateInProgress::ID) {
|
||||||
auto syncStateInProgress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(result.sync_state_);
|
auto syncStateInProgress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(result->sync_state_);
|
||||||
int32_t currentDelta = syncStateInProgress->current_seqno_ - syncStateInProgress->from_seqno_;
|
int32_t currentDelta = syncStateInProgress->current_seqno_ - syncStateInProgress->from_seqno_;
|
||||||
int32_t fullDelta = syncStateInProgress->to_seqno_ - syncStateInProgress->from_seqno_;
|
int32_t fullDelta = syncStateInProgress->to_seqno_ - syncStateInProgress->from_seqno_;
|
||||||
if (currentDelta > 0 && fullDelta > 0) {
|
if (currentDelta > 0 && fullDelta > 0) {
|
||||||
@ -345,10 +345,10 @@ typedef enum {
|
|||||||
} else {
|
} else {
|
||||||
syncStateUpdated(0.0f);
|
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);
|
syncStateUpdated(1.0f);
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
NSNumber *requestId = @(response.id);
|
NSNumber *requestId = @(response.id);
|
||||||
[requestHandlersLock lock];
|
[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>(
|
auto query = make_object<tonlib_api::init>(make_object<tonlib_api::options>(
|
||||||
make_object<tonlib_api::config>(
|
make_object<tonlib_api::config>(
|
||||||
configString.UTF8String,
|
configString.UTF8String,
|
||||||
blockchainName.UTF8String,
|
blockchainName.UTF8String,
|
||||||
enableExternalRequests,
|
enableExternalRequests,
|
||||||
false
|
ignoreCache
|
||||||
),
|
),
|
||||||
make_object<tonlib_api::keyStoreTypeDirectory>(
|
make_object<tonlib_api::keyStoreTypeDirectory>(
|
||||||
keystoreDirectory.UTF8String
|
keystoreDirectory.UTF8String
|
||||||
|
@ -55,7 +55,7 @@ private final class TonInstanceImpl {
|
|||||||
private let blockchainName: String
|
private let blockchainName: String
|
||||||
private let proxy: TonNetworkProxy?
|
private let proxy: TonNetworkProxy?
|
||||||
private var instance: TON?
|
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?) {
|
init(queue: Queue, basePath: String, config: String, blockchainName: String, proxy: TonNetworkProxy?) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
@ -98,6 +98,18 @@ public final class TonInstance {
|
|||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let impl: QueueLocalObject<TonInstanceImpl>
|
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?) {
|
public init(basePath: String, config: String, blockchainName: String, proxy: TonNetworkProxy?) {
|
||||||
self.queue = .mainQueue()
|
self.queue = .mainQueue()
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -10,14 +10,22 @@ import WalletCore
|
|||||||
private final class WalletConfigurationScreenArguments {
|
private final class WalletConfigurationScreenArguments {
|
||||||
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void
|
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void
|
||||||
let dismissInput: () -> 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.updateState = updateState
|
||||||
self.dismissInput = dismissInput
|
self.dismissInput = dismissInput
|
||||||
|
self.updateSelectedMode = updateSelectedMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum WalletConfigurationScreenMode {
|
||||||
|
case `default`
|
||||||
|
case customString
|
||||||
|
}
|
||||||
|
|
||||||
private enum WalletConfigurationScreenSection: Int32 {
|
private enum WalletConfigurationScreenSection: Int32 {
|
||||||
|
case mode
|
||||||
case configString
|
case configString
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,10 +42,14 @@ private enum WalletConfigurationScreenEntryTag: ItemListItemTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
|
private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
|
||||||
|
case modeDefault(WalletTheme, String, Bool)
|
||||||
|
case modeCustomString(WalletTheme, String, Bool)
|
||||||
case configString(WalletTheme, String, String)
|
case configString(WalletTheme, String, String)
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .modeDefault, .modeCustomString:
|
||||||
|
return WalletConfigurationScreenSection.mode.rawValue
|
||||||
case .configString:
|
case .configString:
|
||||||
return WalletConfigurationScreenSection.configString.rawValue
|
return WalletConfigurationScreenSection.configString.rawValue
|
||||||
}
|
}
|
||||||
@ -45,8 +57,12 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
|
|||||||
|
|
||||||
var stableId: Int32 {
|
var stableId: Int32 {
|
||||||
switch self {
|
switch self {
|
||||||
case .configString:
|
case .modeDefault:
|
||||||
return 0
|
return 0
|
||||||
|
case .modeCustomString:
|
||||||
|
return 1
|
||||||
|
case .configString:
|
||||||
|
return 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +73,14 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
|
|||||||
func item(_ arguments: Any) -> ListViewItem {
|
func item(_ arguments: Any) -> ListViewItem {
|
||||||
let arguments = arguments as! WalletConfigurationScreenArguments
|
let arguments = arguments as! WalletConfigurationScreenArguments
|
||||||
switch self {
|
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):
|
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
|
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
|
arguments.updateState { state in
|
||||||
@ -72,17 +96,31 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct WalletConfigurationScreenState: Equatable {
|
private struct WalletConfigurationScreenState: Equatable {
|
||||||
|
var mode: WalletConfigurationScreenMode
|
||||||
var configString: String
|
var configString: String
|
||||||
|
|
||||||
var isEmpty: Bool {
|
var isEmpty: Bool {
|
||||||
|
switch self.mode {
|
||||||
|
case .default:
|
||||||
|
return false
|
||||||
|
case .customString:
|
||||||
return self.configString.isEmpty
|
return self.configString.isEmpty
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func walletConfigurationScreenEntries(presentationData: WalletPresentationData, state: WalletConfigurationScreenState) -> [WalletConfigurationScreenEntry] {
|
private func walletConfigurationScreenEntries(presentationData: WalletPresentationData, state: WalletConfigurationScreenState) -> [WalletConfigurationScreenEntry] {
|
||||||
var entries: [WalletConfigurationScreenEntry] = []
|
var entries: [WalletConfigurationScreenEntry] = []
|
||||||
|
|
||||||
|
entries.append(.modeDefault(presentationData.theme, "Default", state.mode == .default))
|
||||||
|
entries.append(.modeCustomString(presentationData.theme, "Custom", state.mode == .customString))
|
||||||
|
|
||||||
|
switch state.mode {
|
||||||
|
case .default:
|
||||||
|
break
|
||||||
|
case .customString:
|
||||||
entries.append(.configString(presentationData.theme, "", state.configString))
|
entries.append(.configString(presentationData.theme, "", state.configString))
|
||||||
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
@ -104,7 +142,7 @@ func walletConfigurationScreen(context: WalletContext, currentConfiguration: Cus
|
|||||||
configString = string
|
configString = string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let initialState = WalletConfigurationScreenState(configString: configString)
|
let initialState = WalletConfigurationScreenState(mode: currentConfiguration == nil ? .default : .customString, configString: configString)
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void = { f in
|
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void = { f in
|
||||||
@ -122,18 +160,32 @@ func walletConfigurationScreen(context: WalletContext, currentConfiguration: Cus
|
|||||||
updateState(f)
|
updateState(f)
|
||||||
}, dismissInput: {
|
}, dismissInput: {
|
||||||
dismissInputImpl?()
|
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())
|
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
|
||||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> 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 rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Configuration_Apply), style: .bold, enabled: !state.isEmpty, action: {
|
||||||
let state = stateValue.with { $0 }
|
let state = stateValue.with { $0 }
|
||||||
let configuration: CustomWalletConfiguration?
|
let configuration: CustomWalletConfiguration?
|
||||||
|
switch state.mode {
|
||||||
|
case .default:
|
||||||
|
configuration = nil
|
||||||
|
case .customString:
|
||||||
if state.configString.isEmpty {
|
if state.configString.isEmpty {
|
||||||
configuration = nil
|
configuration = nil
|
||||||
} else {
|
} else {
|
||||||
configuration = .string(state.configString)
|
configuration = .string(state.configString)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
context.storage.updateCustomWalletConfiguration(configuration)
|
context.storage.updateCustomWalletConfiguration(configuration)
|
||||||
dismissImpl?()
|
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)
|
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.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||||
controller.experimentalSnapScrollToItem = true
|
controller.experimentalSnapScrollToItem = true
|
||||||
controller.didAppear = { _ in
|
controller.didAppear = { _ in
|
||||||
|
@ -233,7 +233,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
|||||||
private let hasActions: Bool
|
private let hasActions: Bool
|
||||||
|
|
||||||
let balanceNode: WalletInfoBalanceNode
|
let balanceNode: WalletInfoBalanceNode
|
||||||
private let refreshNode: WalletRefreshNode
|
let refreshNode: WalletRefreshNode
|
||||||
private let balanceSubtitleNode: ImmediateTextNode
|
private let balanceSubtitleNode: ImmediateTextNode
|
||||||
private let receiveButtonNode: SolidRoundedButtonNode
|
private let receiveButtonNode: SolidRoundedButtonNode
|
||||||
private let receiveGramsButtonNode: SolidRoundedButtonNode
|
private let receiveGramsButtonNode: SolidRoundedButtonNode
|
||||||
@ -552,6 +552,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
private var pollCombinedStateDisposable: Disposable?
|
private var pollCombinedStateDisposable: Disposable?
|
||||||
private var watchCombinedStateDisposable: 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) {
|
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
|
self.context = context
|
||||||
@ -690,6 +691,17 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
self.pollCombinedStateDisposable = (pollCombinedState
|
self.pollCombinedStateDisposable = (pollCombinedState
|
||||||
|> deliverOnMainQueue).start()
|
|> 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 {
|
deinit {
|
||||||
@ -698,6 +710,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
self.updateTimestampTimer?.invalidate()
|
self.updateTimestampTimer?.invalidate()
|
||||||
self.pollCombinedStateDisposable?.dispose()
|
self.pollCombinedStateDisposable?.dispose()
|
||||||
self.watchCombinedStateDisposable?.dispose()
|
self.watchCombinedStateDisposable?.dispose()
|
||||||
|
self.refreshProgressDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollToHideHeader() {
|
func scrollToHideHeader() {
|
||||||
@ -775,6 +788,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
|
|||||||
self.reloadingState = true
|
self.reloadingState = true
|
||||||
|
|
||||||
self.headerNode.isRefreshing = true
|
self.headerNode.isRefreshing = true
|
||||||
|
self.headerNode.refreshNode.refreshProgress = 0.0
|
||||||
|
|
||||||
let subject: CombinedWalletStateSubject
|
let subject: CombinedWalletStateSubject
|
||||||
if let walletInfo = self.walletInfo {
|
if let walletInfo = self.walletInfo {
|
||||||
|
@ -123,6 +123,8 @@ final class WalletRefreshNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var state: WalletRefreshState?
|
private var state: WalletRefreshState?
|
||||||
|
|
||||||
|
var refreshProgress: Float = 0.0
|
||||||
|
|
||||||
private let animator: ConstantDisplayLinkAnimator
|
private let animator: ConstantDisplayLinkAnimator
|
||||||
|
|
||||||
init(strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat) {
|
init(strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat) {
|
||||||
@ -212,10 +214,15 @@ final class WalletRefreshNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var cachedProgress: Float = 0.0
|
||||||
|
|
||||||
func update(state: WalletRefreshState) {
|
func update(state: WalletRefreshState) {
|
||||||
if self.state == state {
|
if self.state == state && self.cachedProgress == self.refreshProgress {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let ignoreProgressValue = self.refreshProgress == 0.0 || (self.cachedProgress == 0.0 && self.refreshProgress == 1.0)
|
||||||
|
self.cachedProgress = self.refreshProgress
|
||||||
|
|
||||||
let previousState = self.state
|
let previousState = self.state
|
||||||
self.state = 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))
|
title = lastUpdateTimestampString(strings: self.strings, dateTimeFormat: dateTimeFormat, statusTimestamp: ts, relativeTo: Int32(Date().timeIntervalSince1970))
|
||||||
pullProgress = progress
|
pullProgress = progress
|
||||||
case .refreshing:
|
case .refreshing:
|
||||||
|
if ignoreProgressValue {
|
||||||
title = self.strings.Wallet_Info_Updating
|
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 {
|
if let previousState = previousState {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user