diff --git a/submodules/Display/Display/ListView.swift b/submodules/Display/Display/ListView.swift index 814081c18d..0c510a2d1f 100644 --- a/submodules/Display/Display/ListView.swift +++ b/submodules/Display/Display/ListView.swift @@ -609,7 +609,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.resetHeaderItemsFlashTimer(start: true) self.updateHeaderItemsFlashing(animated: true) self.resetScrollIndicatorFlashTimer(start: true) - self.didEndScrolling?() + if !scrollView.isTracking { + self.didEndScrolling?() + } } public func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -3945,6 +3947,16 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } + public func scrollToOffsetFromTop(_ offset: CGFloat) -> Bool { + for itemNode in self.itemNodes { + if itemNode.index == 0 { + self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: true) + return true + } + } + return false + } + public func scrollWithDirection(_ direction: ListViewScrollDirection, distance: CGFloat) -> Bool { var accessibilityFocusedNode: (ASDisplayNode, CGRect)? for itemNode in self.itemNodes { diff --git a/submodules/WalletUI/Resources/Animations/WalletInitializing.tgs b/submodules/WalletUI/Resources/Animations/WalletInitializing.tgs new file mode 100644 index 0000000000..74bbb0bf3d Binary files /dev/null and b/submodules/WalletUI/Resources/Animations/WalletInitializing.tgs differ diff --git a/submodules/WalletUI/Resources/WalletStrings.mapping b/submodules/WalletUI/Resources/WalletStrings.mapping index a109ac414e..7495ee2901 100644 Binary files a/submodules/WalletUI/Resources/WalletStrings.mapping and b/submodules/WalletUI/Resources/WalletStrings.mapping differ diff --git a/submodules/WalletUI/Sources/ItemList/Items/ItemListDisclosureItem.swift b/submodules/WalletUI/Sources/ItemList/Items/ItemListDisclosureItem.swift new file mode 100644 index 0000000000..7b661a8b41 --- /dev/null +++ b/submodules/WalletUI/Sources/ItemList/Items/ItemListDisclosureItem.swift @@ -0,0 +1,516 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit + +enum ItemListDisclosureItemTitleColor { + case primary + case accent +} + +enum ItemListDisclosureStyle { + case arrow + case none +} + +enum ItemListDisclosureLabelStyle { + case text + case detailText + case multilineDetailText + case badge(UIColor) + case color(UIColor) +} + +class ItemListDisclosureItem: ListViewItem, ItemListItem { + let theme: WalletTheme + let icon: UIImage? + let title: String + let titleColor: ItemListDisclosureItemTitleColor + let enabled: Bool + let label: String + let labelStyle: ItemListDisclosureLabelStyle + let sectionId: ItemListSectionId + let style: ItemListStyle + let disclosureStyle: ItemListDisclosureStyle + let action: (() -> Void)? + let clearHighlightAutomatically: Bool + let tag: ItemListItemTag? + + init(theme: WalletTheme, icon: UIImage? = nil, title: String, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, label: String, labelStyle: ItemListDisclosureLabelStyle = .text, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil) { + self.theme = theme + self.icon = icon + self.title = title + self.titleColor = titleColor + self.enabled = enabled + self.labelStyle = labelStyle + self.label = label + self.sectionId = sectionId + self.style = style + self.disclosureStyle = disclosureStyle + self.action = action + self.clearHighlightAutomatically = clearHighlightAutomatically + self.tag = tag + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ItemListDisclosureItemNode() + 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() }) + }) + } + } + } + + 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? ItemListDisclosureItemNode { + 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() + }) + } + } + } + } + } + + var selectable: Bool = true + + func selected(listView: ListView){ + if self.clearHighlightAutomatically { + listView.clearHighlightAnimated(true) + } + if self.enabled { + self.action?() + } + } +} + +private let titleFont = Font.regular(17.0) +private let badgeFont = Font.regular(15.0) +private let detailFont = Font.regular(13.0) + +class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode + + let iconNode: ASImageNode + let titleNode: TextNode + let labelNode: TextNode + let arrowNode: ASImageNode + let labelBadgeNode: ASImageNode + let labelImageNode: ASImageNode + + private let activateArea: AccessibilityAreaNode + + private var item: ItemListDisclosureItem? + + override var canBeSelected: Bool { + if let item = self.item, let _ = item.action { + return true + } else { + return false + } + } + + var tag: ItemListItemTag? { + return self.item?.tag + } + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.backgroundColor = .white + + self.maskNode = ASImageNode() + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displaysAsynchronously = false + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + + self.labelNode = TextNode() + self.labelNode.isUserInteractionEnabled = false + + self.arrowNode = ASImageNode() + self.arrowNode.displayWithoutProcessing = true + self.arrowNode.displaysAsynchronously = false + self.arrowNode.isLayerBacked = true + + self.labelBadgeNode = ASImageNode() + self.labelImageNode = ASImageNode() + self.labelBadgeNode.displayWithoutProcessing = true + self.labelBadgeNode.displaysAsynchronously = false + self.labelBadgeNode.isLayerBacked = true + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.activateArea = AccessibilityAreaNode() + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.titleNode) + self.addSubnode(self.labelNode) + self.addSubnode(self.arrowNode) + + self.addSubnode(self.activateArea) + } + + func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeLabelLayout = TextNode.asyncLayout(self.labelNode) + + let currentItem = self.item + + let currentHasBadge = self.labelBadgeNode.image != nil + + return { item, params, neighbors in + let rightInset: CGFloat + switch item.disclosureStyle { + case .none: + rightInset = 16.0 + params.rightInset + case .arrow: + rightInset = 34.0 + params.rightInset + } + + var updateArrowImage: UIImage? + var updatedTheme: WalletTheme? + + var updatedLabelBadgeImage: UIImage? + var updatedLabelImage: UIImage? + + var badgeColor: UIColor? + if case let .badge(color) = item.labelStyle { + if item.label.count > 0 { + badgeColor = color + } + } + if case let .color(color) = item.labelStyle { + var updatedColor = true + if let currentItem = currentItem, case let .color(previousColor) = currentItem.labelStyle, color.isEqual(previousColor) { + updatedColor = false + } + if updatedColor { + updatedLabelImage = generateFilledCircleImage(diameter: 17.0, color: color) + } + } + + let badgeDiameter: CGFloat = 20.0 + if currentItem?.theme !== item.theme { + updatedTheme = item.theme + updateArrowImage = disclosureArrowImage(item.theme) + if let badgeColor = badgeColor { + updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor) + } + } else if let badgeColor = badgeColor, !currentHasBadge { + updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor) + } + + var updateIcon = false + if currentItem?.icon != item.icon { + updateIcon = true + } + + let contentSize: CGSize + let insets: UIEdgeInsets + let separatorHeight = UIScreenPixel + let itemBackgroundColor: UIColor + let itemSeparatorColor: UIColor + + var leftInset = 16.0 + params.leftInset + if let _ = item.icon { + leftInset += 43.0 + } + + let titleColor: UIColor + if item.enabled { + titleColor = item.titleColor == .accent ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor + } else { + titleColor = item.theme.list.itemDisabledTextColor + } + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let labelFont: UIFont + let labelBadgeColor: UIColor + var labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0 + switch item.labelStyle { + case .badge: + labelBadgeColor = item.theme.list.plainBackgroundColor + labelFont = badgeFont + case .detailText, .multilineDetailText: + labelBadgeColor = item.theme.list.itemSecondaryTextColor + labelFont = detailFont + labelConstrain = params.width - params.rightInset - 40.0 - leftInset + default: + labelBadgeColor = item.theme.list.itemSecondaryTextColor + labelFont = titleFont + } + var multilineLabel = false + if case .multilineDetailText = item.labelStyle { + multilineLabel = true + } + + let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor:labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let height: CGFloat + switch item.labelStyle { + case .detailText: + height = 64.0 + case .multilineDetailText: + height = 44.0 + labelLayout.size.height + default: + height = 44.0 + } + + switch item.style { + case .plain: + itemBackgroundColor = item.theme.list.plainBackgroundColor + itemSeparatorColor = item.theme.list.itemPlainSeparatorColor + contentSize = CGSize(width: params.width, height: height) + insets = itemListNeighborsPlainInsets(neighbors) + case .blocks: + itemBackgroundColor = item.theme.list.itemBlocksBackgroundColor + itemSeparatorColor = item.theme.list.itemBlocksSeparatorColor + contentSize = CGSize(width: params.width, height: height) + insets = itemListNeighborsGroupedInsets(neighbors) + } + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in + if let strongSelf = self { + strongSelf.item = item + + 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)) + strongSelf.activateArea.accessibilityLabel = item.title + strongSelf.activateArea.accessibilityValue = item.label + if item.enabled { + strongSelf.activateArea.accessibilityTraits = [] + } else { + strongSelf.activateArea.accessibilityTraits = .notEnabled + } + + if let icon = item.icon { + if strongSelf.iconNode.supernode == nil { + strongSelf.addSubnode(strongSelf.iconNode) + } + if updateIcon { + strongSelf.iconNode.image = icon + } + let iconY: CGFloat + if case .multilineDetailText = item.labelStyle { + iconY = 14.0 + } else { + iconY = floor((layout.contentSize.height - icon.size.height) / 2.0) + } + strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - icon.size.width) / 2.0), y: iconY), size: icon.size) + } else if strongSelf.iconNode.supernode != nil { + strongSelf.iconNode.image = nil + strongSelf.iconNode.removeFromSupernode() + } + + if let updateArrowImage = updateArrowImage { + strongSelf.arrowNode.image = updateArrowImage + } + + if let _ = updatedTheme { + strongSelf.topStripeNode.backgroundColor = itemSeparatorColor + strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor + strongSelf.backgroundNode.backgroundColor = itemBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor + } + + let _ = titleApply() + let _ = labelApply() + + switch item.style { + case .plain: + if strongSelf.backgroundNode.supernode != nil { + strongSelf.backgroundNode.removeFromSupernode() + } + if strongSelf.topStripeNode.supernode != nil { + strongSelf.topStripeNode.removeFromSupernode() + } + if strongSelf.bottomStripeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) + } + if strongSelf.maskNode.supernode != nil { + strongSelf.maskNode.removeFromSupernode() + } + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) + case .blocks: + 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 + 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) + + if let updateBadgeImage = updatedLabelBadgeImage { + if strongSelf.labelBadgeNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.labelBadgeNode, belowSubnode: strongSelf.labelNode) + } + strongSelf.labelBadgeNode.image = updateBadgeImage + } + if badgeColor == nil && strongSelf.labelBadgeNode.supernode != nil { + strongSelf.labelBadgeNode.image = nil + strongSelf.labelBadgeNode.removeFromSupernode() + } + + let badgeWidth = max(badgeDiameter, labelLayout.size.width + 10.0) + strongSelf.labelBadgeNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth, y: 12.0), size: CGSize(width: badgeWidth, height: badgeDiameter)) + + let labelFrame: CGRect + switch item.labelStyle { + case .badge: + labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: 13.0), size: labelLayout.size) + case .detailText, .multilineDetailText: + labelFrame = CGRect(origin: CGPoint(x: leftInset, y: 36.0), size: labelLayout.size) + default: + labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: 11.0), size: labelLayout.size) + } + strongSelf.labelNode.frame = labelFrame + + if case .color = item.labelStyle { + if let updatedLabelImage = updatedLabelImage { + strongSelf.labelImageNode.image = updatedLabelImage + } + if strongSelf.labelImageNode.supernode == nil { + strongSelf.addSubnode(strongSelf.labelImageNode) + } + if let image = strongSelf.labelImageNode.image { + strongSelf.labelImageNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 50.0, y: floor((layout.contentSize.height - image.size.height) / 2.0)), size: image.size) + } + } else if strongSelf.labelImageNode.supernode != nil { + strongSelf.labelImageNode.removeFromSupernode() + strongSelf.labelImageNode.image = nil + } + + if let arrowImage = strongSelf.arrowNode.image { + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + } + + switch item.disclosureStyle { + case .none: + strongSelf.arrowNode.isHidden = true + case .arrow: + strongSelf.arrowNode.isHidden = false + } + + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel)) + } + }) + } + } + + override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + if highlighted && (self.item?.enabled ?? false) { + 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 func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateAdded(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} diff --git a/submodules/WalletUI/Sources/WalletConfgurationScreen.swift b/submodules/WalletUI/Sources/WalletConfgurationScreen.swift index 84f3cd74be..7df2cf13e2 100644 --- a/submodules/WalletUI/Sources/WalletConfgurationScreen.swift +++ b/submodules/WalletUI/Sources/WalletConfgurationScreen.swift @@ -11,22 +11,25 @@ private final class WalletConfigurationScreenArguments { let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void let dismissInput: () -> Void let updateSelectedMode: (WalletConfigurationScreenMode) -> Void + let updateBlockchainName: (String) -> Void - init(updateState: @escaping ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void, dismissInput: @escaping () -> Void, updateSelectedMode: @escaping (WalletConfigurationScreenMode) -> Void) { + init(updateState: @escaping ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void, dismissInput: @escaping () -> Void, updateSelectedMode: @escaping (WalletConfigurationScreenMode) -> Void, updateBlockchainName: @escaping (String) -> Void) { self.updateState = updateState self.dismissInput = dismissInput self.updateSelectedMode = updateSelectedMode + self.updateBlockchainName = updateBlockchainName } } private enum WalletConfigurationScreenMode { - case `default` + case url case customString } private enum WalletConfigurationScreenSection: Int32 { case mode case configString + case blockchainName } private enum WalletConfigurationScreenEntryTag: ItemListItemTag { @@ -42,27 +45,38 @@ private enum WalletConfigurationScreenEntryTag: ItemListItemTag { } private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable { - case modeDefault(WalletTheme, String, Bool) + case modeUrl(WalletTheme, String, Bool) case modeCustomString(WalletTheme, String, Bool) + case configUrl(WalletTheme, WalletStrings, String, String) case configString(WalletTheme, String, String) + case blockchainNameHeader(WalletTheme, String) + case blockchainName(WalletTheme, WalletStrings, String, String) var section: ItemListSectionId { switch self { - case .modeDefault, .modeCustomString: + case .modeUrl, .modeCustomString: return WalletConfigurationScreenSection.mode.rawValue - case .configString: + case .configUrl, .configString: return WalletConfigurationScreenSection.configString.rawValue + case .blockchainNameHeader, .blockchainName: + return WalletConfigurationScreenSection.blockchainName.rawValue } } var stableId: Int32 { switch self { - case .modeDefault: + case .modeUrl: return 0 case .modeCustomString: return 1 - case .configString: + case .configUrl: return 2 + case .configString: + return 3 + case .blockchainNameHeader: + return 4 + case .blockchainName: + return 5 } } @@ -73,14 +87,22 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable { func item(_ arguments: Any) -> ListViewItem { let arguments = arguments as! WalletConfigurationScreenArguments switch self { - case let .modeDefault(theme, text, isSelected): + case let .modeUrl(theme, text, isSelected): return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { - arguments.updateSelectedMode(.default) + arguments.updateSelectedMode(.url) }) 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 .configUrl(theme, strings, placeholder, text): + return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in + arguments.updateState { state in + var state = state + state.configUrl = value + return state + } + }, action: {}) 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 @@ -91,18 +113,29 @@ private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable { }, shouldUpdateText: { _ in return true }, processPaste: nil, updatedFocus: nil, tag: WalletConfigurationScreenEntryTag.configStringText, action: nil, inlineAction: nil) + case let .blockchainNameHeader(theme, title): + return ItemListSectionHeaderItem(theme: theme, text: title, sectionId: self.section) + case let .blockchainName(theme, strings, title, value): + return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: value, placeholder: title, sectionId: self.section, textUpdated: { value in + arguments.updateBlockchainName(value) + }, action: {}) } } } private struct WalletConfigurationScreenState: Equatable { var mode: WalletConfigurationScreenMode + var configUrl: String var configString: String + var blockchainName: String var isEmpty: Bool { + if self.blockchainName.isEmpty { + return true + } switch self.mode { - case .default: - return false + case .url: + return self.configUrl.isEmpty || URL(string: self.configUrl) == nil case .customString: return self.configString.isEmpty } @@ -112,16 +145,19 @@ private struct WalletConfigurationScreenState: Equatable { private func walletConfigurationScreenEntries(presentationData: WalletPresentationData, state: WalletConfigurationScreenState) -> [WalletConfigurationScreenEntry] { var entries: [WalletConfigurationScreenEntry] = [] - entries.append(.modeDefault(presentationData.theme, "Default", state.mode == .default)) + entries.append(.modeUrl(presentationData.theme, "URL", state.mode == .url)) entries.append(.modeCustomString(presentationData.theme, "Custom", state.mode == .customString)) switch state.mode { - case .default: - break + case .url: + entries.append(.configUrl(presentationData.theme, presentationData.strings, "URL", state.configUrl)) case .customString: - entries.append(.configString(presentationData.theme, "", state.configString)) + entries.append(.configString(presentationData.theme, "JSON", state.configString)) } + entries.append(.blockchainNameHeader(presentationData.theme, "BLOCKCHAIN NAME")) + entries.append(.blockchainName(presentationData.theme, presentationData.strings, "Blockchain Name", state.blockchainName)) + return entries } @@ -134,15 +170,16 @@ private final class WalletConfigurationScreenImpl: ItemListController, WalletCon } } -func walletConfigurationScreen(context: WalletContext, currentConfiguration: CustomWalletConfiguration?) -> ViewController { +func walletConfigurationScreen(context: WalletContext, currentConfiguration: LocalWalletConfiguration) -> ViewController { + var configUrl = "" var configString = "" - if let currentConfiguration = currentConfiguration { - switch currentConfiguration { - case let .string(string): - configString = string - } + switch currentConfiguration.source { + case let .url(url): + configUrl = url + case let .string(string): + configString = string } - let initialState = WalletConfigurationScreenState(mode: currentConfiguration == nil ? .default : .customString, configString: configString) + let initialState = WalletConfigurationScreenState(mode: configString.isEmpty ? .url : .customString, configUrl: configUrl, configString: configString, blockchainName: currentConfiguration.blockchainName) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void = { f in @@ -166,6 +203,12 @@ func walletConfigurationScreen(context: WalletContext, currentConfiguration: Cus state.mode = mode return state } + }, updateBlockchainName: { value in + updateState { state in + var state = state + state.blockchainName = value + return state + } }) let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get()) @@ -175,19 +218,34 @@ func walletConfigurationScreen(context: WalletContext, currentConfiguration: Cus }) let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Configuration_Apply), style: .bold, enabled: !state.isEmpty, action: { let state = stateValue.with { $0 } - let configuration: CustomWalletConfiguration? + let source: LocalWalletConfigurationSource + let blockchainName = state.blockchainName + if blockchainName.isEmpty { + return + } switch state.mode { - case .default: - configuration = nil + case .url: + if state.configUrl.isEmpty { + return + } else { + source = .url(state.configUrl) + } case .customString: if state.configString.isEmpty { - configuration = nil + return } else { - configuration = .string(state.configString) + source = .string(state.configString) } } - context.storage.updateCustomWalletConfiguration(configuration) - dismissImpl?() + let _ = (context.storage.updateLocalWalletConfiguration { current in + var current = current + current.source = source + current.blockchainName = blockchainName + return current + } + |> deliverOnMainQueue).start(completed: { + dismissImpl?() + }) }) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_Configuration_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false) diff --git a/submodules/WalletUI/Sources/WalletContext.swift b/submodules/WalletUI/Sources/WalletContext.swift index 86905df74b..f87a235d27 100644 --- a/submodules/WalletUI/Sources/WalletContext.swift +++ b/submodules/WalletUI/Sources/WalletContext.swift @@ -15,6 +15,8 @@ public protocol WalletContext { var presentationData: WalletPresentationData { get } var supportsCustomConfigurations: Bool { get } + var termsUrl: String? { get } + var feeInfoUrl: String? { get } var inForeground: Signal { get } diff --git a/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift b/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift index e53540b078..1e8583ee53 100644 --- a/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift +++ b/submodules/WalletUI/Sources/WalletInfoEmptyNode.swift @@ -10,14 +10,16 @@ class WalletInfoEmptyItem: ListViewItem { let theme: WalletTheme let strings: WalletStrings let address: String + let loading: Bool let displayAddressContextMenu: (ASDisplayNode, CGRect) -> Void let selectable: Bool = false - init(theme: WalletTheme, strings: WalletStrings, address: String, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) { + init(theme: WalletTheme, strings: WalletStrings, address: String, loading: Bool, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) { self.theme = theme self.strings = strings self.address = address + self.loading = loading self.displayAddressContextMenu = displayAddressContextMenu } @@ -66,10 +68,6 @@ final class WalletInfoEmptyItemNode: ListViewItemNode { self.offsetContainer = ASDisplayNode() self.animationNode = AnimatedStickerNode() - if let path = getAppBundle().path(forResource: "WalletEmpty", ofType: "tgs") { - self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 280, height: 280, playbackMode: .once, mode: .direct) - self.animationNode.visibility = true - } self.titleNode = TextNode() self.titleNode.displaysAsynchronously = false @@ -158,13 +156,26 @@ final class WalletInfoEmptyItemNode: ListViewItemNode { let textFrame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.size.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textLayout.size) let addressFrame = CGRect(origin: CGPoint(x: floor((params.width - addressLayout.size.width) / 2.0), y: textFrame.maxY + titleSpacing), size: addressLayout.size) - let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: addressFrame.maxY + 32.0), insets: UIEdgeInsets()) + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: (item.loading ? iconFrame.maxY : addressFrame.maxY) + 32.0), insets: UIEdgeInsets()) return (layout, { guard let strongSelf = self else { return } - strongSelf.item = item + + if strongSelf.item?.loading != item.loading { + if item.loading { + if let path = getAppBundle().path(forResource: "WalletInitializing", ofType: "tgs") { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 280, height: 280, playbackMode: .loop, mode: .direct) + strongSelf.animationNode.visibility = true + } + } else { + if let path = getAppBundle().path(forResource: "WalletEmpty", ofType: "tgs") { + strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 280, height: 280, playbackMode: .once, mode: .direct) + strongSelf.animationNode.visibility = true + } + } + } strongSelf.item = item @@ -182,6 +193,10 @@ final class WalletInfoEmptyItemNode: ListViewItemNode { transition.updateFrameAdditive(node: strongSelf.titleNode, frame: titleFrame) transition.updateFrameAdditive(node: strongSelf.textNode, frame: textFrame) transition.updateFrameAdditive(node: strongSelf.addressNode, frame: addressFrame) + + strongSelf.titleNode.isHidden = item.loading + strongSelf.textNode.isHidden = item.loading + strongSelf.addressNode.isHidden = item.loading strongSelf.contentSize = layout.contentSize strongSelf.insets = layout.insets diff --git a/submodules/WalletUI/Sources/WalletInfoScreen.swift b/submodules/WalletUI/Sources/WalletInfoScreen.swift index cf3c021cb1..de9d764802 100644 --- a/submodules/WalletUI/Sources/WalletInfoScreen.swift +++ b/submodules/WalletUI/Sources/WalletInfoScreen.swift @@ -103,9 +103,24 @@ public final class WalletInfoScreen: ViewController { guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else { return } - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - strongSelf.push(walletSendScreen(context: strongSelf.context, randomId: randomId, walletInfo: walletInfo)) + guard let combinedState = (strongSelf.displayNode as! WalletInfoScreenNode).combinedState else { + return + } + if (strongSelf.displayNode as! WalletInfoScreenNode).reloadingState { + strongSelf.present(standardTextAlertController(theme: strongSelf.presentationData.theme.alert, title: nil, text: strongSelf.presentationData.strings.Wallet_Send_SyncInProgress, actions: [ + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Wallet_Alert_OK, action: { + }) + ]), in: .window(.root)) + } else if !combinedState.pendingTransactions.isEmpty { + strongSelf.present(standardTextAlertController(theme: strongSelf.presentationData.theme.alert, title: nil, text: strongSelf.presentationData.strings.Wallet_Send_TransactionInProgress, actions: [ + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Wallet_Alert_OK, action: { + }) + ]), in: .window(.root)) + } else { + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + strongSelf.push(walletSendScreen(context: strongSelf.context, randomId: randomId, walletInfo: walletInfo)) + } }, receiveAction: { [weak self] in guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else { return @@ -141,6 +156,7 @@ final class WalletInfoBalanceNode: ASDisplayNode { let balanceIntegralTextNode: ImmediateTextNode let balanceFractionalTextNode: ImmediateTextNode let balanceIconNode: AnimatedStickerNode + private var balanceIconNodeIsStatic: Bool var balance: (String, UIColor) = (" ", .white) { didSet { @@ -179,6 +195,7 @@ final class WalletInfoBalanceNode: ASDisplayNode { self.balanceIconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct) self.balanceIconNode.visibility = true } + self.balanceIconNodeIsStatic = true super.init() @@ -209,14 +226,33 @@ final class WalletInfoBalanceNode: ASDisplayNode { balanceFractionalTextFrame.origin.x -= (balanceFractionalTextFrame.width / 4.0) * scaleTransition + 0.25 * (balanceFractionalTextFrame.width / 2.0) * (1.0 - scaleTransition) balanceFractionalTextFrame.origin.y += balanceFractionalTextFrame.height * 0.5 * (0.8 - fractionalScale) + let isBalanceEmpty = self.balance.0.isEmpty || self.balance.0 == " " + let balanceIconFrame: CGRect - balanceIconFrame = CGRect(origin: CGPoint(x: apparentBalanceIntegralTextFrame.minX - balanceIconSize.width - balanceIconSpacing * integralScale, y: balanceIntegralTextFrame.midY - balanceIconSize.height / 2.0 + balanceVerticalIconOffset), size: balanceIconSize) + if isBalanceEmpty { + balanceIconFrame = CGRect(origin: CGPoint(x: floor((width - balanceIconSize.width) / 2.0), y: balanceIntegralTextFrame.midY - balanceIconSize.height / 2.0 + balanceVerticalIconOffset), size: balanceIconSize) + } else { + balanceIconFrame = CGRect(origin: CGPoint(x: apparentBalanceIntegralTextFrame.minX - balanceIconSize.width - balanceIconSpacing * integralScale, y: balanceIntegralTextFrame.midY - balanceIconSize.height / 2.0 + balanceVerticalIconOffset), size: balanceIconSize) + } transition.updateFrameAsPositionAndBounds(node: self.balanceIntegralTextNode, frame: balanceIntegralTextFrame) transition.updateTransformScale(node: self.balanceIntegralTextNode, scale: integralScale) transition.updateFrameAsPositionAndBounds(node: self.balanceFractionalTextNode, frame: balanceFractionalTextFrame) transition.updateTransformScale(node: self.balanceFractionalTextNode, scale: fractionalScale) + if !isBalanceEmpty != self.balanceIconNodeIsStatic { + self.balanceIconNodeIsStatic = !isBalanceEmpty + if isBalanceEmpty { + if let path = getAppBundle().path(forResource: "WalletIntroLoading", ofType: "tgs") { + self.balanceIconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct) + } + } else { + if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") { + self.balanceIconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct) + } + } + } + self.balanceIconNode.updateLayout(size: balanceIconFrame.size) transition.updateFrameAsPositionAndBounds(node: self.balanceIconNode, frame: balanceIconFrame) transition.updateTransformScale(node: self.balanceIconNode, scale: scaleTransition * 1.0 + (1.0 - scaleTransition) * 0.8) @@ -319,7 +355,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode { let effectiveOffset = max(offset, navigationHeight) - let minButtonsOffset = maxOffset - buttonHeight - sideInset + let minButtonsOffset = maxOffset - buttonHeight * 2.0 - sideInset let maxButtonsOffset = maxOffset let buttonTransition: CGFloat = max(0.0, min(1.0, (effectiveOffset - minButtonsOffset) / (maxButtonsOffset - minButtonsOffset))) let buttonAlpha: CGFloat = buttonTransition @@ -344,11 +380,11 @@ private final class WalletInfoHeaderNode: ASDisplayNode { let refreshSize = CGSize(width: 0.0, height: 0.0) transition.updateFrame(node: self.refreshNode, frame: CGRect(origin: CGPoint(x: floor((size.width - refreshSize.width) / 2.0), y: navigationHeight - 44.0 + floor((44.0 - refreshSize.height) / 2.0)), size: refreshSize)) - transition.updateAlpha(node: self.refreshNode, alpha: headerScaleTransition) - if self.balance == nil { - self.refreshNode.update(state: .pullToRefresh(self.timestamp ?? 0, 0.0)) - } else if self.isRefreshing { + transition.updateAlpha(node: self.refreshNode, alpha: headerScaleTransition, beginWithCurrentState: true) + if self.isRefreshing { self.refreshNode.update(state: .refreshing) + } else if self.balance == nil { + self.refreshNode.update(state: .pullToRefresh(self.timestamp ?? 0, 0.0)) } else { let refreshOffset: CGFloat = 20.0 let refreshScaleTransition: CGFloat = max(0.0, (offset - maxOffset) / refreshOffset) @@ -379,7 +415,7 @@ private final class WalletInfoHeaderNode: ASDisplayNode { self.sendButtonNode.isHidden = false } else { if self.balance == nil { - self.receiveGramsButtonNode.isHidden = true + self.receiveGramsButtonNode.isHidden = false self.receiveButtonNode.isHidden = true self.sendButtonNode.isHidden = true } else { @@ -389,9 +425,9 @@ private final class WalletInfoHeaderNode: ASDisplayNode { } } if self.balance == nil { - self.balanceNode.isHidden = true + self.balanceNode.isHidden = false self.balanceSubtitleNode.isHidden = true - self.refreshNode.isHidden = true + self.refreshNode.isHidden = false } else { self.balanceNode.isHidden = false self.balanceSubtitleNode.isHidden = false @@ -399,13 +435,13 @@ private final class WalletInfoHeaderNode: ASDisplayNode { } transition.updateFrame(node: self.receiveGramsButtonNode, frame: fullButtonFrame) - transition.updateAlpha(node: self.receiveGramsButtonNode, alpha: buttonAlpha) + transition.updateAlpha(node: self.receiveGramsButtonNode, alpha: buttonAlpha, beginWithCurrentState: true) transition.updateFrame(node: self.receiveButtonNode, frame: leftButtonFrame) - transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha) + transition.updateAlpha(node: self.receiveButtonNode, alpha: buttonAlpha, beginWithCurrentState: true) self.receiveGramsButtonNode.updateLayout(width: fullButtonFrame.width, transition: transition) self.receiveButtonNode.updateLayout(width: leftButtonFrame.width, transition: transition) transition.updateFrame(node: self.sendButtonNode, frame: sendButtonFrame) - transition.updateAlpha(node: self.sendButtonNode, alpha: buttonAlpha) + transition.updateAlpha(node: self.sendButtonNode, alpha: buttonAlpha, beginWithCurrentState: true) self.sendButtonNode.updateLayout(width: sendButtonFrame.width, transition: transition) } @@ -453,7 +489,7 @@ private enum WalletInfoListEntryId: Hashable { } private enum WalletInfoListEntry: Equatable, Comparable, Identifiable { - case empty(String) + case empty(String, Bool) case transaction(Int, WalletInfoTransaction) var stableId: WalletInfoListEntryId { @@ -491,8 +527,8 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable { func item(theme: WalletTheme, strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat, action: @escaping (WalletInfoTransaction) -> Void, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) -> ListViewItem { switch self { - case let .empty(address): - return WalletInfoEmptyItem(theme: theme, strings: strings, address: address, displayAddressContextMenu: { node, frame in + case let .empty(address, loading): + return WalletInfoEmptyItem(theme: theme, strings: strings, address: address, loading: loading, displayAddressContextMenu: { node, frame in displayAddressContextMenu(node, frame) }) case let .transaction(_, transaction): @@ -526,7 +562,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { private let headerNode: WalletInfoHeaderNode private let listNode: ListView - private let loadingIndicator: UIActivityIndicatorView private var enqueuedTransactions: [WalletInfoListTransaction] = [] @@ -536,7 +571,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { private let transactionListDisposable = MetaDisposable() private var listOffset: CGFloat? - private var reloadingState: Bool = false + private(set) var reloadingState: Bool = false private var loadingMoreTransactions: Bool = false private var canLoadMoreTransactions: Bool = true @@ -567,18 +602,15 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { self.listNode = ListView() self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3) self.listNode.verticalScrollIndicatorFollowsOverscroll = true - self.listNode.isHidden = true + self.listNode.isHidden = false self.listNode.view.disablesInteractiveModalDismiss = true - self.loadingIndicator = UIActivityIndicatorView(style: .whiteLarge) - super.init() self.backgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor self.addSubnode(self.listNode) self.addSubnode(self.headerNode) - self.view.addSubview(self.loadingIndicator) var canBeginRefresh = true var isScrolling = false @@ -630,11 +662,11 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { switch strongSelf.listNode.visibleContentOffset() { case let .known(offset): if offset < strongSelf.listNode.insets.top { - /*if offset > strongSelf.listNode.insets.top / 2.0 { + if offset > strongSelf.listNode.insets.top / 2.0 { strongSelf.scrollToHideHeader() } else { strongSelf.scrollToTop() - }*/ + } } default: break @@ -717,20 +749,19 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { guard let (_, navigationHeight) = self.validLayout else { return } - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(navigationHeight), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + let _ = self.listNode.scrollToOffsetFromTop(self.headerNode.frame.maxY - navigationHeight) } func scrollToTop() { - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + if !self.listNode.scrollToOffsetFromTop(0.0) { + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } } func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { 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 @@ -809,17 +840,9 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { if strongSelf.combinedState != nil { return } - if state == nil { - strongSelf.loadingIndicator.startAnimating() - } else { - strongSelf.loadingIndicator.stopAnimating() - strongSelf.loadingIndicator.isHidden = true - } combinedState = state case let .updated(state): isUpdated = true - strongSelf.loadingIndicator.stopAnimating() - strongSelf.loadingIndicator.isHidden = true combinedState = state } @@ -877,7 +900,9 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) } - self.reloadingState = false + if isUpdated { + self.reloadingState = false + } self.headerNode.timestamp = Int32(clamping: combinedState.timestamp) @@ -909,7 +934,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { } } - self.transactionsLoaded(isReload: true, transactions: updatedTransactions, pendingTransactions: combinedState.pendingTransactions) + self.transactionsLoaded(isReload: true, isEmpty: false, transactions: updatedTransactions, pendingTransactions: combinedState.pendingTransactions) if isUpdated { self.headerNode.isRefreshing = false @@ -918,17 +943,19 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { if self.isReady, let (layout, navigationHeight) = self.validLayout { self.headerNode.update(size: self.headerNode.bounds.size, navigationHeight: navigationHeight, offset: self.listOffset ?? 0.0, transition: .animated(duration: 0.2, curve: .easeInOut), isScrolling: false) } - - let wasReady = self.isReady - self.isReady = self.combinedState != nil - - if self.isReady && !wasReady { - if let (layout, navigationHeight) = self.validLayout { - self.headerNode.update(size: self.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate, isScrolling: false) - } - - self.becameReady(animated: self.didSetContentReady) + } else { + self.transactionsLoaded(isReload: true, isEmpty: true, transactions: [], pendingTransactions: []) + } + + let wasReady = self.isReady + self.isReady = true + + if self.isReady && !wasReady { + if let (layout, navigationHeight) = self.validLayout { + self.headerNode.update(size: self.headerNode.bounds.size, navigationHeight: navigationHeight, offset: layout.size.height, transition: .immediate, isScrolling: false) } + + self.becameReady(animated: self.didSetContentReady) } if !self.didSetContentReady { @@ -961,7 +988,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { guard let strongSelf = self else { return } - strongSelf.transactionsLoaded(isReload: false, transactions: transactions, pendingTransactions: []) + strongSelf.transactionsLoaded(isReload: false, isEmpty: false, transactions: transactions, pendingTransactions: []) }, error: { [weak self] _ in guard let strongSelf = self else { return @@ -969,9 +996,11 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { })) } - private func transactionsLoaded(isReload: Bool, transactions: [WalletTransaction], pendingTransactions: [PendingWalletTransaction]) { - self.loadingMoreTransactions = false - self.canLoadMoreTransactions = transactions.count > 2 + private func transactionsLoaded(isReload: Bool, isEmpty: Bool, transactions: [WalletTransaction], pendingTransactions: [PendingWalletTransaction]) { + if !isEmpty { + self.loadingMoreTransactions = false + self.canLoadMoreTransactions = transactions.count > 2 + } var updatedEntries: [WalletInfoListEntry] = [] if isReload { @@ -989,7 +1018,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { } } if updatedEntries.isEmpty { - updatedEntries.append(.empty(self.address)) + updatedEntries.append(.empty(self.address, isEmpty)) } } else { updatedEntries = self.currentEntries ?? [] @@ -1016,7 +1045,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { } } if updatedEntries.isEmpty { - updatedEntries.append(.empty(self.address)) + updatedEntries.append(.empty(self.address, false)) } } @@ -1065,8 +1094,6 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode { private func becameReady(animated: Bool) { self.listNode.isHidden = false - 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 ? .animated(duration: 0.5, curve: .spring) : .immediate) diff --git a/submodules/WalletUI/Sources/WalletPresentationData.swift b/submodules/WalletUI/Sources/WalletPresentationData.swift index 5ad8dd9cda..5721a386ec 100644 --- a/submodules/WalletUI/Sources/WalletPresentationData.swift +++ b/submodules/WalletUI/Sources/WalletPresentationData.swift @@ -219,6 +219,7 @@ enum WalletThemeResourceKey: Int32 { case itemListCornersTop case itemListCornersBottom case itemListClearInputIcon + case itemListDisclosureArrow case navigationShareIcon case clockMin @@ -269,7 +270,13 @@ func itemListClearInputIcon(_ theme: WalletTheme) -> UIImage? { func navigationShareIcon(_ theme: WalletTheme) -> UIImage? { return theme.image(WalletThemeResourceKey.navigationShareIcon.rawValue, { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationShare"), color: theme.navigationBar.buttonColor) + generateTintedImage(image: UIImage(bundleImageName: "Wallet/NavigationShare"), color: theme.navigationBar.buttonColor) + }) +} + +func disclosureArrowImage(_ theme: WalletTheme) -> UIImage? { + return theme.image(WalletThemeResourceKey.itemListDisclosureArrow.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Wallet/DisclosureArrow"), color: theme.list.itemSecondaryTextColor) }) } diff --git a/submodules/WalletUI/Sources/WalletSettingsScreen.swift b/submodules/WalletUI/Sources/WalletSettingsScreen.swift index b8678189ee..c670a5cde3 100644 --- a/submodules/WalletUI/Sources/WalletSettingsScreen.swift +++ b/submodules/WalletUI/Sources/WalletSettingsScreen.swift @@ -111,7 +111,7 @@ public func walletSettingsController(context: WalletContext, walletInfo: WalletI var replaceAllWalletControllersImpl: ((ViewController) -> Void)? let arguments = WalletSettingsControllerArguments(openConfiguration: { - let _ = (context.storage.customWalletConfiguration() + let _ = (context.storage.localWalletConfiguration() |> take(1) |> deliverOnMainQueue).start(next: { configuration in pushControllerImpl?(walletConfigurationScreen(context: context, currentConfiguration: configuration)) diff --git a/submodules/WalletUI/Sources/WalletSplashScreen.swift b/submodules/WalletUI/Sources/WalletSplashScreen.swift index b316165c8c..9c8b97c9d4 100644 --- a/submodules/WalletUI/Sources/WalletSplashScreen.swift +++ b/submodules/WalletUI/Sources/WalletSplashScreen.swift @@ -20,7 +20,7 @@ public enum WalletSecureStorageResetReason { public enum WalletSplashMode { case intro case created(WalletInfo, [String]?) - case success(WalletInfo, Bool) + case success(WalletInfo) case restoreFailed case sending(WalletInfo, String, Int64, Data, Int64, Data) case sent(WalletInfo, Int64) @@ -58,7 +58,7 @@ public final class WalletSplashScreen: ViewController { return .single(nil) }) } - case let .success(walletInfo, _): + case let .success(walletInfo): if let walletCreatedPreloadState = walletCreatedPreloadState { self.walletCreatedPreloadState = walletCreatedPreloadState } else { @@ -397,78 +397,31 @@ public final class WalletSplashScreen: ViewController { } }) } - case let .success(walletInfo, _): + case let .success(walletInfo): let _ = (walletAddress(publicKey: walletInfo.publicKey, tonInstance: strongSelf.context.tonInstance) |> deliverOnMainQueue).start(next: { address in guard let strongSelf = self else { return } - - var stateSignal: Signal - if let walletCreatedPreloadState = strongSelf.walletCreatedPreloadState { - stateSignal = walletCreatedPreloadState.get() - |> map { state -> Bool in - if let state = state { - if case .updated = state { - return true - } else { - return false - } - } else { - return true - } - } - |> filter { $0 } - } else { - stateSignal = .single(true) - } - - let presentationData = strongSelf.presentationData - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) - self?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - stateSignal = stateSignal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - let _ = (stateSignal - |> deliverOnMainQueue).start(next: { _ in - guard let strongSelf = self else { - return - } - progressDisposable.dispose() - if let navigationController = strongSelf.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { controller in - if controller is WalletSplashScreen { - return false - } - if controller is WalletWordDisplayScreen { - return false - } - if controller is WalletWordCheckScreen { - return false - } - return true + if let navigationController = strongSelf.navigationController as? NavigationController { + var controllers = navigationController.viewControllers + controllers = controllers.filter { controller in + if controller is WalletSplashScreen { + return false } - controllers.append(WalletInfoScreen(context: strongSelf.context, walletInfo: walletInfo, address: address, enableDebugActions: false)) - strongSelf.view.endEditing(true) - navigationController.setViewControllers(controllers, animated: true) + if controller is WalletWordDisplayScreen { + return false + } + if controller is WalletWordCheckScreen { + return false + } + return true } - }) + controllers.append(WalletInfoScreen(context: strongSelf.context, walletInfo: walletInfo, address: address, enableDebugActions: false)) + strongSelf.view.endEditing(true) + navigationController.setViewControllers(controllers, animated: true) + } }) case let .sent(walletInfo, _): if let navigationController = strongSelf.navigationController as? NavigationController { @@ -601,20 +554,8 @@ public final class WalletSplashScreen: ViewController { guard let strongSelf = self else { return } - var url = strongSelf.presentationData.strings.Wallet_Intro_TermsUrl - if url.isEmpty { - url = "https://telegram.org/tos/wallet" - } - strongSelf.context.openUrl(url) - }, replaceWithSynchronized: { [weak self] in - guard let strongSelf = self else { - return - } - switch strongSelf.mode { - case let .success(walletInfo, _): - (strongSelf.navigationController as? NavigationController)?.replaceTopController(WalletSplashScreen(context: strongSelf.context, mode: .success(walletInfo, true), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState), animated: true) - default: - break + if let url = strongSelf.context.termsUrl { + strongSelf.context.openUrl(url) } }) @@ -632,7 +573,6 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { private var presentationData: WalletPresentationData private let mode: WalletSplashMode private let secondaryAction: () -> Void - private let replaceWithSynchronized: () -> Void private let iconNode: ASImageNode private var animationSize: CGSize = CGSize() @@ -658,11 +598,10 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { } } - init(context: WalletContext, walletCreatedPreloadState: Promise?, presentationData: WalletPresentationData, mode: WalletSplashMode, action: @escaping () -> Void, secondaryAction: @escaping () -> Void, openTerms: @escaping () -> Void, replaceWithSynchronized: @escaping () -> Void) { + init(context: WalletContext, walletCreatedPreloadState: Promise?, presentationData: WalletPresentationData, mode: WalletSplashMode, action: @escaping () -> Void, secondaryAction: @escaping () -> Void, openTerms: @escaping () -> Void) { self.presentationData = presentationData self.mode = mode self.secondaryAction = secondaryAction - self.replaceWithSynchronized = replaceWithSynchronized self.iconNode = ASImageNode() self.iconNode.displayWithoutProcessing = true @@ -688,10 +627,10 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { buttonText = self.presentationData.strings.Wallet_Intro_CreateWallet let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor, additionalAttributes: [:]) let link = MarkdownAttributeSet(font: Font.regular(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor, additionalAttributes: [NSAttributedString.Key.underlineStyle.rawValue: NSUnderlineStyle.single.rawValue as NSNumber]) - if context.supportsCustomConfigurations { - termsText = NSAttributedString(string: "") - } else { + if let _ = context.termsUrl { termsText = parseMarkdownIntoAttributedString(self.presentationData.strings.Wallet_Intro_Terms, attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center) + } else { + termsText = NSAttributedString(string: "") } self.iconNode.image = nil if let path = getAppBundle().path(forResource: "WalletIntroLoading", ofType: "tgs") { @@ -712,35 +651,19 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { self.animationNode.visibility = true } secondaryActionText = "" - case let .success(_, synchronized): - if synchronized { - title = self.presentationData.strings.Wallet_Completed_Title - text = NSAttributedString(string: self.presentationData.strings.Wallet_Completed_Text, font: textFont, textColor: textColor) - buttonText = self.presentationData.strings.Wallet_Completed_ViewWallet - termsText = NSAttributedString(string: "") - self.iconNode.image = nil - if let path = getAppBundle().path(forResource: "WalletDone", ofType: "tgs") { - self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, playbackMode: .loop, mode: .direct) - self.animationSize = CGSize(width: 130.0, height: 130.0) - self.animationOffset = CGPoint(x: 0.0, y: 0.0) - self.animationNode.visibility = true - } - secondaryActionText = "" - } else { - title = self.presentationData.strings.Wallet_Completed_ProgressTitle - text = NSAttributedString(string: self.presentationData.strings.Wallet_Completed_ProgressText, font: textFont, textColor: textColor) - buttonText = self.presentationData.strings.Wallet_Completed_ViewWallet - buttonHidden = true - termsText = NSAttributedString(string: "") - self.iconNode.image = nil - if let path = getAppBundle().path(forResource: "WalletSynchronization", ofType: "tgs") { - self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 220, height: 220, playbackMode: .loop, mode: .direct) - self.animationSize = CGSize(width: 110.0, height: 110.0) - self.animationOffset = CGPoint(x: -10.0, y: 0.0) - self.animationNode.visibility = true - } - secondaryActionText = "" + case .success: + title = self.presentationData.strings.Wallet_Completed_Title + text = NSAttributedString(string: self.presentationData.strings.Wallet_Completed_Text, font: textFont, textColor: textColor) + buttonText = self.presentationData.strings.Wallet_Completed_ViewWallet + termsText = NSAttributedString(string: "") + self.iconNode.image = nil + if let path = getAppBundle().path(forResource: "WalletDone", ofType: "tgs") { + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, playbackMode: .loop, mode: .direct) + self.animationSize = CGSize(width: 130.0, height: 130.0) + self.animationOffset = CGPoint(x: 0.0, y: 0.0) + self.animationNode.visibility = true } + secondaryActionText = "" case .restoreFailed: title = self.presentationData.strings.Wallet_RestoreFailed_Title text = NSAttributedString(string: self.presentationData.strings.Wallet_RestoreFailed_Text, font: textFont, textColor: textColor) @@ -912,97 +835,6 @@ private final class WalletSplashScreenNode: ViewControllerTracingNode { openTerms() } } - - switch mode { - case let .success(walletInfo, synchronized) where !synchronized: - var stateSignal: Signal - if let walletCreatedPreloadState = walletCreatedPreloadState { - stateSignal = walletCreatedPreloadState.get() - |> map { state -> Bool in - if let state = state { - if case .updated = state { - return true - } else { - return false - } - } else { - return true - } - } - |> filter { $0 } - |> take(1) - } else { - stateSignal = .single(true) - } - self.stateDisposable = (stateSignal - |> deliverOnMainQueue).start(next: { [weak self] _ in - guard let strongSelf = self else { - return - } - - if strongSelf.validLayout != nil { - strongSelf.replaceWithSynchronized() - return - } - - strongSelf.buttonNode.isHidden = false - - let title = strongSelf.presentationData.strings.Wallet_Completed_Title - let text = NSAttributedString(string: strongSelf.presentationData.strings.Wallet_Completed_Text, font: textFont, textColor: textColor) - - strongSelf.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: strongSelf.presentationData.theme.list.itemPrimaryTextColor) - strongSelf.textNode.attributedText = text - - if let path = getAppBundle().path(forResource: "WalletDone", ofType: "tgs") { - let animateIn = !strongSelf.animationNode.frame.isEmpty - var snapshotView: UIView? - if animateIn { - snapshotView = strongSelf.animationNode.view.snapshotContentTree() - } - - strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 260, height: 260, playbackMode: .once, mode: .direct) - strongSelf.animationSize = CGSize(width: 130.0, height: 130.0) - strongSelf.animationOffset = CGPoint(x: 14.0, y: 0.0) - strongSelf.animationNode.visibility = true - - if animateIn { - if let snapshotView = snapshotView { - strongSelf.view.insertSubview(snapshotView, belowSubview: strongSelf.animationNode.view) - snapshotView.frame = strongSelf.animationNode.frame - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - snapshotView.layer.animateScale(from: 1.0, to: 0.7, duration: 0.18, removeOnCompletion: false) - } - strongSelf.animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.08) - strongSelf.animationNode.layer.animateScale(from: 0.7, to: 1.0, duration: 0.2) - } - } - - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) - } - }) - - self.synchronizationProgressDisposable = (context.tonInstance.syncProgress - |> deliverOnMainQueue).start(next: { [weak self] progress in - guard let strongSelf = self, strongSelf.buttonNode.isHidden else { - return - } - - let percent = Int(progress * 100.0) - - let title = strongSelf.presentationData.strings.Wallet_Completed_ProgressTitle + " \(percent)%" - - strongSelf.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: strongSelf.presentationData.theme.list.itemPrimaryTextColor) - - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) - } - }) - default: - break - } } deinit { diff --git a/submodules/WalletUI/Sources/WalletStrings.swift b/submodules/WalletUI/Sources/WalletStrings.swift index 5b027c9bf8..e3188ce4e7 100644 --- a/submodules/WalletUI/Sources/WalletStrings.swift +++ b/submodules/WalletUI/Sources/WalletStrings.swift @@ -204,88 +204,88 @@ public final class WalletStrings: Equatable { public var Wallet_Month_ShortJune: String { return self._s[13]! } public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[14]! } public var Wallet_Created_Title: String { return self._s[15]! } - public var Wallet_Info_YourBalance: String { return self._s[16]! } - public var Wallet_TransactionInfo_CommentHeader: String { return self._s[17]! } - public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[18]! } + public var Wallet_Send_SyncInProgress: String { return self._s[16]! } + public var Wallet_Info_YourBalance: String { return self._s[17]! } + public var Wallet_TransactionInfo_CommentHeader: String { return self._s[18]! } + public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[19]! } public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[19]!, self._r[19]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[20]!, self._r[20]!, [_1, _2, _3]) } - public var Wallet_WordImport_IncorrectText: String { return self._s[20]! } - public var Wallet_Month_GenJanuary: String { return self._s[21]! } - public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[22]! } - public var Wallet_Receive_ShareAddress: String { return self._s[23]! } - public var Wallet_WordImport_Title: String { return self._s[24]! } - public var Wallet_TransactionInfo_Title: String { return self._s[25]! } - public var Wallet_Words_NotDoneText: String { return self._s[27]! } - public var Wallet_RestoreFailed_EnterWords: String { return self._s[28]! } - public var Wallet_WordImport_Text: String { return self._s[29]! } - public var Wallet_RestoreFailed_Text: String { return self._s[31]! } - public var Wallet_TransactionInfo_NoAddress: String { return self._s[32]! } - public var Wallet_Navigation_Back: String { return self._s[33]! } - public var Wallet_Intro_Terms: String { return self._s[34]! } + public var Wallet_WordImport_IncorrectText: String { return self._s[21]! } + public var Wallet_Month_GenJanuary: String { return self._s[22]! } + public var Wallet_Send_OwnAddressAlertTitle: String { return self._s[23]! } + public var Wallet_Receive_ShareAddress: String { return self._s[24]! } + public var Wallet_WordImport_Title: String { return self._s[25]! } + public var Wallet_TransactionInfo_Title: String { return self._s[26]! } + public var Wallet_Words_NotDoneText: String { return self._s[28]! } + public var Wallet_RestoreFailed_EnterWords: String { return self._s[29]! } + public var Wallet_WordImport_Text: String { return self._s[30]! } + public var Wallet_RestoreFailed_Text: String { return self._s[32]! } + public var Wallet_TransactionInfo_NoAddress: String { return self._s[33]! } + public var Wallet_Navigation_Back: String { return self._s[34]! } + public var Wallet_Intro_Terms: String { return self._s[35]! } public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[35]!, self._r[35]!, [_0]) + return formatWithArgumentRanges(self._s[36]!, self._r[36]!, [_0]) } public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[36]!, self._r[36]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[37]!, self._r[37]!, [_1, _2, _3]) } - public var Wallet_TransactionInfo_AddressCopied: String { return self._s[37]! } + public var Wallet_TransactionInfo_AddressCopied: String { return self._s[38]! } public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[38]!, self._r[38]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[39]!, self._r[39]!, [_1, _2, _3]) } - public var Wallet_Send_NetworkErrorText: String { return self._s[39]! } - public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[40]! } - public var Wallet_Intro_ImportExisting: String { return self._s[41]! } - public var Wallet_Receive_CommentInfo: String { return self._s[42]! } - public var Wallet_WordCheck_Continue: String { return self._s[43]! } - public var Wallet_Completed_ProgressText: String { return self._s[44]! } + public var Wallet_Send_NetworkErrorText: String { return self._s[40]! } + public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[41]! } + public var Wallet_Intro_ImportExisting: String { return self._s[42]! } + public var Wallet_Receive_CommentInfo: String { return self._s[43]! } + public var Wallet_WordCheck_Continue: String { return self._s[44]! } public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[45]! } public var Wallet_Completed_Text: String { return self._s[46]! } public var Wallet_WordCheck_IncorrectHeader: String { return self._s[48]! } - public var Wallet_Receive_Title: String { return self._s[49]! } - public var Wallet_Info_WalletCreated: String { return self._s[50]! } - public var Wallet_Navigation_Cancel: String { return self._s[51]! } - public var Wallet_CreateInvoice_Title: String { return self._s[52]! } + public var Wallet_TransactionInfo_StorageFeeInfoUrl: String { return self._s[49]! } + public var Wallet_Receive_Title: String { return self._s[50]! } + public var Wallet_Info_WalletCreated: String { return self._s[51]! } + public var Wallet_Navigation_Cancel: String { return self._s[52]! } + public var Wallet_CreateInvoice_Title: String { return self._s[53]! } public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[53]!, self._r[53]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[54]!, self._r[54]!, [_1, _2, _3]) } - public var Wallet_TransactionInfo_SenderHeader: String { return self._s[54]! } + public var Wallet_TransactionInfo_SenderHeader: String { return self._s[55]! } public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[55]!, self._r[55]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[56]!, self._r[56]!, [_1, _2, _3]) } - public var Wallet_Month_GenAugust: String { return self._s[56]! } - public var Wallet_Info_UnknownTransaction: String { return self._s[57]! } - public var Wallet_Receive_CreateInvoice: String { return self._s[58]! } - public var Wallet_Month_GenSeptember: String { return self._s[59]! } - public var Wallet_Month_GenJuly: String { return self._s[60]! } - public var Wallet_Receive_AddressHeader: String { return self._s[61]! } - public var Wallet_Send_AmountText: String { return self._s[62]! } - public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[63]! } + public var Wallet_Month_GenAugust: String { return self._s[57]! } + public var Wallet_Info_UnknownTransaction: String { return self._s[58]! } + public var Wallet_Receive_CreateInvoice: String { return self._s[59]! } + public var Wallet_Month_GenSeptember: String { return self._s[60]! } + public var Wallet_Month_GenJuly: String { return self._s[61]! } + public var Wallet_Receive_AddressHeader: String { return self._s[62]! } + public var Wallet_Send_AmountText: String { return self._s[63]! } + public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[64]! } public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[64]!, self._r[64]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[65]!, self._r[65]!, [_1, _2, _3]) } public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[65]!, self._r[65]!, [_0]) + return formatWithArgumentRanges(self._s[66]!, self._r[66]!, [_0]) } - public var Wallet_Configuration_Title: String { return self._s[67]! } - public var Wallet_Words_Title: String { return self._s[68]! } - public var Wallet_Month_ShortMay: String { return self._s[69]! } - public var Wallet_WordCheck_Title: String { return self._s[70]! } - public var Wallet_Words_NotDoneResponse: String { return self._s[71]! } - public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[72]! } - public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[73]! } + public var Wallet_Configuration_Title: String { return self._s[68]! } + public var Wallet_Words_Title: String { return self._s[69]! } + public var Wallet_Month_ShortMay: String { return self._s[70]! } + public var Wallet_WordCheck_Title: String { return self._s[71]! } + public var Wallet_Words_NotDoneResponse: String { return self._s[72]! } + public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[73]! } + public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[74]! } public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[74]!, self._r[74]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[75]!, self._r[75]!, [_1, _2, _3]) } - public var Wallet_Info_Address: String { return self._s[75]! } - public var Wallet_Intro_CreateWallet: String { return self._s[76]! } - public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[77]! } + public var Wallet_Info_Address: String { return self._s[76]! } + public var Wallet_Intro_CreateWallet: String { return self._s[77]! } + public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[78]! } public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[78]!, self._r[78]!, [_0]) + return formatWithArgumentRanges(self._s[79]!, self._r[79]!, [_0]) } - public var Wallet_Send_SendAnyway: String { return self._s[79]! } - public var Wallet_UnknownError: String { return self._s[80]! } - public var Wallet_Completed_ProgressTitle: String { return self._s[81]! } + public var Wallet_Send_SendAnyway: String { return self._s[80]! } + public var Wallet_UnknownError: String { return self._s[81]! } public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[82]! } public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[84]! } public var Wallet_Words_NotDoneOk: String { return self._s[85]! } @@ -336,13 +336,13 @@ public final class WalletStrings: Equatable { public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(self._s[124]!, self._r[124]!, [_0]) } - public var Wallet_TransactionInfo_FeeInfoURL: String { return self._s[125]! } public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[126]!, self._r[126]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[125]!, self._r[125]!, [_1, _2, _3]) } - public var Wallet_RestoreFailed_CreateWallet: String { return self._s[127]! } - public var Wallet_Weekday_Yesterday: String { return self._s[128]! } - public var Wallet_Receive_AmountHeader: String { return self._s[129]! } + public var Wallet_RestoreFailed_CreateWallet: String { return self._s[126]! } + public var Wallet_Weekday_Yesterday: String { return self._s[127]! } + public var Wallet_Receive_AmountHeader: String { return self._s[128]! } + public var Wallet_TransactionInfo_OtherFeeInfoUrl: String { return self._s[129]! } public var Wallet_Month_ShortFebruary: String { return self._s[130]! } public var Wallet_Alert_Cancel: String { return self._s[131]! } public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[132]! } @@ -364,41 +364,41 @@ public final class WalletStrings: Equatable { return formatWithArgumentRanges(self._s[147]!, self._r[147]!, [_1, _2, _3]) } public var Wallet_Info_Updating: String { return self._s[149]! } - public var Wallet_Intro_TermsUrl: String { return self._s[150]! } - public var Wallet_Created_ExportErrorTitle: String { return self._s[151]! } - public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[152]! } - public var Wallet_Sending_Title: String { return self._s[153]! } - public var Wallet_Navigation_Done: String { return self._s[154]! } - public var Wallet_Settings_Title: String { return self._s[155]! } + public var Wallet_Created_ExportErrorTitle: String { return self._s[150]! } + public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[151]! } + public var Wallet_Sending_Title: String { return self._s[152]! } + public var Wallet_Navigation_Done: String { return self._s[153]! } + public var Wallet_Settings_Title: String { return self._s[154]! } public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[156]!, self._r[156]!, [_0]) + return formatWithArgumentRanges(self._s[155]!, self._r[155]!, [_0]) } - public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[157]! } - public var Wallet_Weekday_Today: String { return self._s[159]! } - public var Wallet_Month_ShortDecember: String { return self._s[160]! } - public var Wallet_Words_Text: String { return self._s[161]! } - public var Wallet_WordCheck_ViewWords: String { return self._s[162]! } - public var Wallet_Send_AddressInfo: String { return self._s[163]! } + public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[156]! } + public var Wallet_Weekday_Today: String { return self._s[158]! } + public var Wallet_Month_ShortDecember: String { return self._s[159]! } + public var Wallet_Words_Text: String { return self._s[160]! } + public var Wallet_WordCheck_ViewWords: String { return self._s[161]! } + public var Wallet_Send_AddressInfo: String { return self._s[162]! } public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[164]!, self._r[164]!, [_0]) + return formatWithArgumentRanges(self._s[163]!, self._r[163]!, [_0]) } - public var Wallet_Intro_NotNow: String { return self._s[165]! } - public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[166]! } - public var Wallet_Navigation_Close: String { return self._s[167]! } - public var Wallet_Month_GenDecember: String { return self._s[169]! } - public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[170]! } - public var Wallet_WordImport_IncorrectTitle: String { return self._s[171]! } - public var Wallet_Send_AddressText: String { return self._s[172]! } - public var Wallet_Receive_AmountInfo: String { return self._s[173]! } + public var Wallet_Intro_NotNow: String { return self._s[164]! } + public var Wallet_Send_OwnAddressAlertProceed: String { return self._s[165]! } + public var Wallet_Navigation_Close: String { return self._s[166]! } + public var Wallet_Month_GenDecember: String { return self._s[168]! } + public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[169]! } + public var Wallet_WordImport_IncorrectTitle: String { return self._s[170]! } + public var Wallet_Send_AddressText: String { return self._s[171]! } + public var Wallet_Receive_AmountInfo: String { return self._s[172]! } public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) { - return formatWithArgumentRanges(self._s[174]!, self._r[174]!, [_1, _2, _3]) + return formatWithArgumentRanges(self._s[173]!, self._r[173]!, [_1, _2, _3]) } - public var Wallet_Month_ShortAugust: String { return self._s[175]! } - public var Wallet_Qr_Title: String { return self._s[176]! } - public var Wallet_Settings_Configuration: String { return self._s[177]! } - public var Wallet_WordCheck_TryAgain: String { return self._s[178]! } - public var Wallet_Info_TransactionPendingHeader: String { return self._s[179]! } - public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[180]! } + public var Wallet_Month_ShortAugust: String { return self._s[174]! } + public var Wallet_Qr_Title: String { return self._s[175]! } + public var Wallet_Settings_Configuration: String { return self._s[176]! } + public var Wallet_WordCheck_TryAgain: String { return self._s[177]! } + public var Wallet_Info_TransactionPendingHeader: String { return self._s[178]! } + public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[179]! } + public var Wallet_Send_TransactionInProgress: String { return self._s[180]! } public var Wallet_Created_Text: String { return self._s[181]! } public var Wallet_Created_Proceed: String { return self._s[182]! } public var Wallet_Words_Done: String { return self._s[183]! } diff --git a/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift b/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift index d2649670a4..70d4ba4905 100644 --- a/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift +++ b/submodules/WalletUI/Sources/WalletTransactionInfoScreen.swift @@ -8,6 +8,7 @@ import SwiftSignalKit import OverlayStatusController import WalletCore import AnimatedStickerNode +import Markdown private func stringForFullDate(timestamp: Int32, strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat) -> String { var t: time_t = Int(timestamp) @@ -200,12 +201,20 @@ final class WalletTransactionInfoScreen: ViewController { guard let strongSelf = self else { return } - var string = NSMutableAttributedString(string: "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and for processing your transactions. More info", font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) - string.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor(rgb: 0x6bb2ff), range: NSMakeRange(string.string.count - 10, 10)) - let controller = TooltipController(content: .attributedText(string), timeout: 3.0, dismissByTapOutside: true, dismissByTapOutsideSource: false, dismissImmediatelyOnLayoutUpdate: false, arrowOnBottom: false) + let text: NSAttributedString + if let _ = strongSelf.context.feeInfoUrl { + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white, additionalAttributes: [:]) + let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: strongSelf.context.presentationData.theme.alert.accentColor, additionalAttributes: [:]) + text = parseMarkdownIntoAttributedString(strongSelf.context.presentationData.strings.Wallet_TransactionInfo_StorageFeeInfoUrl, attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: .center) + } else { + text = NSAttributedString(string: strongSelf.context.presentationData.strings.Wallet_TransactionInfo_StorageFeeInfo, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) + } + let controller = TooltipController(content: .attributedText(text), timeout: 3.0, dismissByTapOutside: true, dismissByTapOutsideSource: false, dismissImmediatelyOnLayoutUpdate: false, arrowOnBottom: false) controller.dismissed = { [weak self] tappedInside in if let strongSelf = self, tappedInside { - strongSelf.context.openUrl(strongSelf.presentationData.strings.Wallet_TransactionInfo_FeeInfoURL) + if let feeInfoUrl = strongSelf.context.feeInfoUrl { + strongSelf.context.openUrl(feeInfoUrl) + } } } strongSelf.present(controller, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { diff --git a/submodules/WalletUI/Sources/WalletWordCheckScreen.swift b/submodules/WalletUI/Sources/WalletWordCheckScreen.swift index 3def722269..86152a7da9 100644 --- a/submodules/WalletUI/Sources/WalletWordCheckScreen.swift +++ b/submodules/WalletUI/Sources/WalletWordCheckScreen.swift @@ -2137,7 +2137,7 @@ public final class WalletWordCheckScreen: ViewController { return true } let _ = confirmWalletExported(storage: strongSelf.context.storage, publicKey: walletInfo.publicKey).start() - controllers.append(WalletSplashScreen(context: strongSelf.context, mode: .success(walletInfo, false), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState)) + controllers.append(WalletSplashScreen(context: strongSelf.context, mode: .success(walletInfo), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState)) strongSelf.view.endEditing(true) navigationController.setViewControllers(controllers, animated: true) } @@ -2205,7 +2205,7 @@ public final class WalletWordCheckScreen: ViewController { } return true } - controllers.append(WalletSplashScreen(context: strongSelf.context, mode: .success(walletInfo, false), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState)) + controllers.append(WalletSplashScreen(context: strongSelf.context, mode: .success(walletInfo), walletCreatedPreloadState: strongSelf.walletCreatedPreloadState)) strongSelf.view.endEditing(true) navigationController.setViewControllers(controllers, animated: true) } diff --git a/submodules/WalletUI/WalletImages.xcassets/Wallet/DisclosureArrow.imageset/Contents.json b/submodules/WalletUI/WalletImages.xcassets/Wallet/DisclosureArrow.imageset/Contents.json new file mode 100644 index 0000000000..73d58c942a --- /dev/null +++ b/submodules/WalletUI/WalletImages.xcassets/Wallet/DisclosureArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_open.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/WalletImages.xcassets/Wallet/DisclosureArrow.imageset/ic_open.pdf b/submodules/WalletUI/WalletImages.xcassets/Wallet/DisclosureArrow.imageset/ic_open.pdf new file mode 100644 index 0000000000..058efa69b6 Binary files /dev/null and b/submodules/WalletUI/WalletImages.xcassets/Wallet/DisclosureArrow.imageset/ic_open.pdf differ diff --git a/submodules/WalletUI/WalletImages.xcassets/Wallet/NavigationShare.imageset/Contents.json b/submodules/WalletUI/WalletImages.xcassets/Wallet/NavigationShare.imageset/Contents.json new file mode 100644 index 0000000000..bbeea7231a --- /dev/null +++ b/submodules/WalletUI/WalletImages.xcassets/Wallet/NavigationShare.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_share.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/WalletUI/WalletImages.xcassets/Wallet/NavigationShare.imageset/ic_share.pdf b/submodules/WalletUI/WalletImages.xcassets/Wallet/NavigationShare.imageset/ic_share.pdf new file mode 100644 index 0000000000..c372a213f7 Binary files /dev/null and b/submodules/WalletUI/WalletImages.xcassets/Wallet/NavigationShare.imageset/ic_share.pdf differ