mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Wallet UI improvements
This commit is contained in:
parent
8341aecc01
commit
bc22d4bb87
@ -4787,7 +4787,7 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Wallet.Info.TransactionTo" = "to";
|
||||
"Wallet.Info.TransactionFrom" = "from";
|
||||
"Wallet.Info.Updating" = "updating";
|
||||
"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fee";
|
||||
"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fees";
|
||||
"Wallet.Info.TransactionPendingHeader" = "Pending";
|
||||
"Wallet.Qr.ScanCode" = "Scan QR Code";
|
||||
"Wallet.Qr.Title" = "QR Code";
|
||||
@ -4964,3 +4964,5 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@";
|
||||
|
||||
"Wallet.VoiceOver.Editing.ClearText" = "Clear text";
|
||||
|
||||
"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them.";
|
||||
|
@ -157,7 +157,7 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
|
||||
self.strings = strings
|
||||
self.displayCodes = displayCodes
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings)))
|
||||
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
|
@ -324,7 +324,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
if let existingModalContainer = existingModalContainer {
|
||||
modalContainer = existingModalContainer
|
||||
} else {
|
||||
modalContainer = NavigationModalContainer(theme: self.theme, controllerRemoved: { [weak self] controller in
|
||||
modalContainer = NavigationModalContainer(theme: self.theme, flat: navigationLayout.modal[i].flat, controllerRemoved: { [weak self] controller in
|
||||
self?.controllerRemoved(controller)
|
||||
})
|
||||
self.modalContainers.append(modalContainer)
|
||||
@ -427,6 +427,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
var previousModalContainer: NavigationModalContainer?
|
||||
var visibleModalCount = 0
|
||||
var topModalIsFlat = false
|
||||
var topModalDismissProgress: CGFloat = 0.0
|
||||
|
||||
for i in (0 ..< navigationLayout.modal.count).reversed() {
|
||||
@ -483,6 +484,8 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
previousModalContainer = modalContainer
|
||||
}
|
||||
|
||||
topModalIsFlat = modalContainer.flat
|
||||
}
|
||||
|
||||
switch navigationLayout.root {
|
||||
@ -607,7 +610,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
let effectiveRootModalDismissProgress: CGFloat
|
||||
let additionalModalFrameProgress: CGFloat
|
||||
if visibleModalCount == 1 {
|
||||
effectiveRootModalDismissProgress = topModalDismissProgress
|
||||
effectiveRootModalDismissProgress = topModalIsFlat ? 1.0 : topModalDismissProgress
|
||||
additionalModalFrameProgress = 0.0
|
||||
} else if visibleModalCount == 2 {
|
||||
effectiveRootModalDismissProgress = 0.0
|
||||
@ -667,7 +670,10 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
let maxScale: CGFloat
|
||||
let maxOffset: CGFloat
|
||||
if visibleModalCount == 1 {
|
||||
if topModalIsFlat {
|
||||
maxScale = 1.0
|
||||
maxOffset = 0.0
|
||||
} else if visibleModalCount == 1 {
|
||||
maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width
|
||||
maxOffset = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0)
|
||||
} else {
|
||||
|
@ -10,6 +10,7 @@ enum RootNavigationLayout {
|
||||
|
||||
struct ModalContainerLayout {
|
||||
var controllers: [ViewController]
|
||||
var flat: Bool
|
||||
}
|
||||
|
||||
struct NavigationLayout {
|
||||
@ -23,6 +24,7 @@ func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewL
|
||||
for controller in controllers {
|
||||
let requiresModal: Bool
|
||||
var beginsModal: Bool = false
|
||||
var flatModal: Bool = false
|
||||
switch controller.navigationPresentation {
|
||||
case .default:
|
||||
requiresModal = false
|
||||
@ -31,6 +33,10 @@ func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewL
|
||||
case .modal:
|
||||
requiresModal = true
|
||||
beginsModal = true
|
||||
case .flatModal:
|
||||
requiresModal = true
|
||||
beginsModal = true
|
||||
flatModal = true
|
||||
case .modalInLargeLayout:
|
||||
switch layout.metrics.widthClass {
|
||||
case .compact:
|
||||
@ -40,14 +46,17 @@ func makeNavigationLayout(mode: NavigationControllerMode, layout: ContainerViewL
|
||||
}
|
||||
}
|
||||
if requiresModal {
|
||||
controller._presentedInModal = true
|
||||
if beginsModal || modalStack.isEmpty {
|
||||
modalStack.append(ModalContainerLayout(controllers: [controller]))
|
||||
modalStack.append(ModalContainerLayout(controllers: [controller], flat: flatModal))
|
||||
} else {
|
||||
modalStack[modalStack.count - 1].controllers.append(controller)
|
||||
}
|
||||
} else if !modalStack.isEmpty {
|
||||
controller._presentedInModal = true
|
||||
modalStack[modalStack.count - 1].controllers.append(controller)
|
||||
} else {
|
||||
controller._presentedInModal = false
|
||||
rootControllers.append(controller)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import SwiftSignalKit
|
||||
|
||||
final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
private var theme: NavigationControllerTheme
|
||||
let flat: Bool
|
||||
|
||||
private let dim: ASDisplayNode
|
||||
private let scrollNode: ASScrollNode
|
||||
@ -40,8 +41,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
|
||||
init(theme: NavigationControllerTheme, flat: Bool, controllerRemoved: @escaping (ViewController) -> Void) {
|
||||
self.theme = theme
|
||||
self.flat = flat
|
||||
|
||||
self.dim = ASDisplayNode()
|
||||
self.dim.alpha = 0.0
|
||||
@ -316,6 +318,9 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
}
|
||||
|
||||
var topInset: CGFloat = 10.0
|
||||
if self.flat, let preferredSize = controllers.last?.preferredContentSizeForLayout(layout) {
|
||||
topInset = layout.size.height - preferredSize.height
|
||||
}
|
||||
if let statusBarHeight = layout.statusBarHeight {
|
||||
topInset += statusBarHeight
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ final class NavigationModalFrame: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateShades(layout: ContainerViewLayout, progress: CGFloat, additionalProgress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
private func updateShades(layout: ContainerViewLayout, progress: CGFloat, additionalProgress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
var topInset: CGFloat = 0.0
|
||||
if let statusBarHeight = layout.statusBarHeight {
|
||||
|
@ -105,7 +105,7 @@ enum NavigationPreviousAction: Equatable {
|
||||
open class NavigationBar: ASDisplayNode {
|
||||
private var presentationData: NavigationBarPresentationData
|
||||
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||
private var requestedLayout: Bool = false
|
||||
var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in }
|
||||
|
||||
@ -789,16 +789,16 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
if let validLayout = self.validLayout, self.requestedLayout {
|
||||
self.requestedLayout = false
|
||||
self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, transition: .immediate)
|
||||
self.updateLayout(size: validLayout.0, defaultHeight: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, defaultHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.layoutSuspended {
|
||||
return
|
||||
}
|
||||
|
||||
self.validLayout = (size, leftInset, rightInset)
|
||||
self.validLayout = (size, defaultHeight, leftInset, rightInset)
|
||||
|
||||
let leftButtonInset: CGFloat = leftInset + 16.0
|
||||
let backButtonInset: CGFloat = leftInset + 27.0
|
||||
@ -809,7 +809,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
let contentNodeFrame: CGRect
|
||||
switch contentNode.mode {
|
||||
case .replacement:
|
||||
expansionHeight = contentNode.height - 44.0
|
||||
expansionHeight = contentNode.height - defaultHeight
|
||||
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
||||
case .expansion:
|
||||
expansionHeight = contentNode.height
|
||||
@ -821,7 +821,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel))
|
||||
|
||||
let nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0
|
||||
let nominalHeight: CGFloat = self.collapsed ? 32.0 : defaultHeight
|
||||
let contentVerticalOrigin = size.height - nominalHeight - expansionHeight
|
||||
|
||||
var leftTitleInset: CGFloat = leftInset + 1.0
|
||||
@ -1063,16 +1063,16 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public var contentHeight: CGFloat {
|
||||
public func contentHeight(defaultHeight: CGFloat) -> CGFloat {
|
||||
if let contentNode = self.contentNode {
|
||||
switch contentNode.mode {
|
||||
case .expansion:
|
||||
return 44.0 + contentNode.height
|
||||
return defaultHeight + contentNode.height
|
||||
case .replacement:
|
||||
return contentNode.height
|
||||
}
|
||||
} else {
|
||||
return 44.0
|
||||
return defaultHeight
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@ public enum StatusBarStyle {
|
||||
self = .White
|
||||
case .blackOpaque:
|
||||
self = .Black
|
||||
case .darkContent:
|
||||
self = .Black
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,15 @@ import SwiftSignalKit
|
||||
|
||||
public enum TooltipControllerContent: Equatable {
|
||||
case text(String)
|
||||
case attributedText(NSAttributedString)
|
||||
case iconAndText(UIImage, String)
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case let .text(text), let .iconAndText(_, text):
|
||||
return text
|
||||
case let .attributedText(text):
|
||||
return text.string
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,11 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
self.imageNode.image = content.image
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.attributedText = NSAttributedString(string: content.text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center)
|
||||
if case let .attributedText(text) = content {
|
||||
self.textNode.attributedText = text
|
||||
} else {
|
||||
self.textNode.attributedText = NSAttributedString(string: content.text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center)
|
||||
}
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
@ -62,6 +62,7 @@ public enum ViewControllerNavigationPresentation {
|
||||
case `default`
|
||||
case master
|
||||
case modal
|
||||
case flatModal
|
||||
case modalInLargeLayout
|
||||
}
|
||||
|
||||
@ -121,6 +122,7 @@ public enum ViewControllerNavigationPresentation {
|
||||
}
|
||||
|
||||
open var navigationPresentation: ViewControllerNavigationPresentation = .default
|
||||
var _presentedInModal: Bool = false
|
||||
|
||||
public var presentationArguments: Any?
|
||||
|
||||
@ -319,7 +321,14 @@ public enum ViewControllerNavigationPresentation {
|
||||
|
||||
private func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
||||
let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight ?? 44.0)
|
||||
let defaultNavigationBarHeight: CGFloat
|
||||
if self._presentedInModal {
|
||||
defaultNavigationBarHeight = 56.0
|
||||
} else {
|
||||
defaultNavigationBarHeight = 44.0
|
||||
}
|
||||
let navigationBarHeight: CGFloat = statusBarHeight + (self.navigationBar?.contentHeight(defaultHeight: defaultNavigationBarHeight) ?? defaultNavigationBarHeight)
|
||||
|
||||
let navigationBarOffset: CGFloat
|
||||
if statusBarHeight.isZero {
|
||||
navigationBarOffset = 0.0
|
||||
@ -342,7 +351,7 @@ public enum ViewControllerNavigationPresentation {
|
||||
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar {
|
||||
navigationBarFrame.origin.y += contentNode.height + statusBarHeight
|
||||
}
|
||||
navigationBar.updateLayout(size: navigationBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
|
||||
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: defaultNavigationBarHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
|
||||
if !transition.isAnimated {
|
||||
navigationBar.layer.cancelAnimationsRecursive(key: "bounds")
|
||||
navigationBar.layer.cancelAnimationsRecursive(key: "position")
|
||||
|
@ -848,7 +848,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
||||
switch self.contentNodes {
|
||||
case let .standard(node):
|
||||
if let handleNodeContainer = node.handleNodeContainer, handleNodeContainer.isUserInteractionEnabled, handleNodeContainer.frame.insetBy(dx: 0.0, dy: -5.0).contains(point) {
|
||||
if let handleNode = node.handleNode, handleNode.convert(handleNode.bounds, to: self).insetBy(dx: -5.0, dy: -5.0).contains(point) {
|
||||
if let handleNode = node.handleNode, handleNode.convert(handleNode.bounds, to: self).insetBy(dx: -16.0, dy: -16.0).contains(point) {
|
||||
return handleNodeContainer.view
|
||||
} else {
|
||||
return nil
|
||||
|
@ -78,9 +78,17 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
if highlighted {
|
||||
strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.buttonBackgroundNode.alpha = 0.55
|
||||
strongSelf.labelNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.labelNode.alpha = 0.55
|
||||
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.iconNode.alpha = 0.55
|
||||
} else {
|
||||
strongSelf.buttonBackgroundNode.alpha = 1.0
|
||||
strongSelf.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.labelNode.alpha = 1.0
|
||||
strongSelf.labelNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
strongSelf.iconNode.alpha = 1.0
|
||||
strongSelf.iconNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ private final class NetworkTypeManagerImpl {
|
||||
var currentCellularType: CellularNetworkType
|
||||
var cellularTypeObserver: NSObjectProtocol?
|
||||
#endif
|
||||
|
||||
|
||||
init(queue: Queue, updated: @escaping (NetworkType) -> Void) {
|
||||
self.queue = queue
|
||||
self.updated = updated
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,28 +3,60 @@ import UIKit
|
||||
import Display
|
||||
import AppBundle
|
||||
|
||||
public struct PresentationResourcesSettings {
|
||||
public static let editProfile = UIImage(bundleImageName: "Settings/MenuIcons/EditProfile")?.precomposed()
|
||||
public static let proxy = UIImage(bundleImageName: "Settings/MenuIcons/Proxy")?.precomposed()
|
||||
public static let savedMessages = UIImage(bundleImageName: "Settings/MenuIcons/SavedMessages")?.precomposed()
|
||||
public static let recentCalls = UIImage(bundleImageName: "Settings/MenuIcons/RecentCalls")?.precomposed()
|
||||
public static let stickers = UIImage(bundleImageName: "Settings/MenuIcons/Stickers")?.precomposed()
|
||||
|
||||
public static let notifications = UIImage(bundleImageName: "Settings/MenuIcons/Notifications")?.precomposed()
|
||||
public static let security = UIImage(bundleImageName: "Settings/MenuIcons/Security")?.precomposed()
|
||||
public static let dataAndStorage = UIImage(bundleImageName: "Settings/MenuIcons/DataAndStorage")?.precomposed()
|
||||
public static let appearance = UIImage(bundleImageName: "Settings/MenuIcons/Appearance")?.precomposed()
|
||||
public static let language = UIImage(bundleImageName: "Settings/MenuIcons/Language")?.precomposed()
|
||||
|
||||
public static let wallet = generateTintedImage(image: UIImage(bundleImageName: "Settings/MenuIcons/Wallet"), color: UIColor(rgb: 0x1b1b1c))
|
||||
public static let passport = UIImage(bundleImageName: "Settings/MenuIcons/Passport")?.precomposed()
|
||||
public static let watch = UIImage(bundleImageName: "Settings/MenuIcons/Watch")?.precomposed()
|
||||
|
||||
public static let support = UIImage(bundleImageName: "Settings/MenuIcons/Support")?.precomposed()
|
||||
public static let faq = UIImage(bundleImageName: "Settings/MenuIcons/Faq")?.precomposed()
|
||||
|
||||
public static let addAccount = UIImage(bundleImageName: "Settings/MenuIcons/AddAccount")?.precomposed()
|
||||
public static let setPasscode = UIImage(bundleImageName: "Settings/MenuIcons/SetPasscode")?.precomposed()
|
||||
public static let clearCache = UIImage(bundleImageName: "Settings/MenuIcons/ClearCache")?.precomposed()
|
||||
public static let changePhoneNumber = UIImage(bundleImageName: "Settings/MenuIcons/ChangePhoneNumber")?.precomposed()
|
||||
private func drawBorder(context: CGContext, rect: CGRect) {
|
||||
context.setLineWidth(UIScreenPixel)
|
||||
context.setStrokeColor(UIColor(rgb: 0xffffff, alpha: 0.25).cgColor)
|
||||
let path = CGPath(roundedRect: rect.insetBy(dx: UIScreenPixel / 2.0, dy: UIScreenPixel / 2.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||
context.addPath(path)
|
||||
context.strokePath()
|
||||
}
|
||||
|
||||
private func renderIcon(name: String) -> UIImage? {
|
||||
return generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
if let image = UIImage(bundleImageName: name)?.cgImage {
|
||||
context.draw(image, in: bounds)
|
||||
}
|
||||
drawBorder(context: context, rect: bounds)
|
||||
})
|
||||
}
|
||||
|
||||
public struct PresentationResourcesSettings {
|
||||
public static let editProfile = renderIcon(name: "Settings/MenuIcons/EditProfile")
|
||||
public static let proxy = renderIcon(name: "Settings/MenuIcons/Proxy")
|
||||
public static let savedMessages = renderIcon(name: "Settings/MenuIcons/SavedMessages")
|
||||
public static let recentCalls = renderIcon(name: "Settings/MenuIcons/RecentCalls")
|
||||
public static let stickers = renderIcon(name: "Settings/MenuIcons/Stickers")
|
||||
|
||||
public static let notifications = renderIcon(name: "Settings/MenuIcons/Notifications")
|
||||
public static let security = renderIcon(name: "Settings/MenuIcons/Security")
|
||||
public static let dataAndStorage = renderIcon(name: "Settings/MenuIcons/DataAndStorage")
|
||||
public static let appearance = renderIcon(name: "Settings/MenuIcons/Appearance")
|
||||
public static let language = renderIcon(name: "Settings/MenuIcons/Language")
|
||||
|
||||
public static let wallet = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(bounds.insetBy(dx: 5.0, dy: 5.0))
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Settings/MenuIcons/Wallet"), color: UIColor(rgb: 0x1b1b1c))?.cgImage {
|
||||
context.draw(image, in: bounds)
|
||||
}
|
||||
|
||||
drawBorder(context: context, rect: bounds)
|
||||
})
|
||||
|
||||
public static let passport = renderIcon(name: "Settings/MenuIcons/Passport")
|
||||
public static let watch = renderIcon(name: "Settings/MenuIcons/Watch")
|
||||
|
||||
public static let support = renderIcon(name: "Settings/MenuIcons/Support")
|
||||
public static let faq = renderIcon(name: "Settings/MenuIcons/Faq")
|
||||
|
||||
public static let addAccount = renderIcon(name: "Settings/MenuIcons/AddAccount")
|
||||
public static let setPasscode = renderIcon(name: "Settings/MenuIcons/SetPasscode")
|
||||
public static let clearCache = renderIcon(name: "Settings/MenuIcons/ClearCache")
|
||||
public static let changePhoneNumber = renderIcon(name: "Settings/MenuIcons/ChangePhoneNumber")
|
||||
}
|
||||
|
@ -47,34 +47,41 @@ public func stringForFullDate(timestamp: Int32, strings: PresentationStrings, da
|
||||
var timeinfo = tm()
|
||||
localtime_r(&t, &timeinfo);
|
||||
|
||||
let dayString = "\(timeinfo.tm_mday)"
|
||||
let yearString = "\(2000 + timeinfo.tm_year - 100)"
|
||||
let timeString = stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)
|
||||
|
||||
let monthFormat: (String, String, String) -> (String, [(Int, NSRange)])
|
||||
switch timeinfo.tm_mon + 1 {
|
||||
case 1:
|
||||
return strings.Time_PreciseDate_m1("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m1
|
||||
case 2:
|
||||
return strings.Time_PreciseDate_m2("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m2
|
||||
case 3:
|
||||
return strings.Time_PreciseDate_m3("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m3
|
||||
case 4:
|
||||
return strings.Time_PreciseDate_m4("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m4
|
||||
case 5:
|
||||
return strings.Time_PreciseDate_m5("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m5
|
||||
case 6:
|
||||
return strings.Time_PreciseDate_m6("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m6
|
||||
case 7:
|
||||
return strings.Time_PreciseDate_m7("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m7
|
||||
case 8:
|
||||
return strings.Time_PreciseDate_m8("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m8
|
||||
case 9:
|
||||
return strings.Time_PreciseDate_m9("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m9
|
||||
case 10:
|
||||
return strings.Time_PreciseDate_m10("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m10
|
||||
case 11:
|
||||
return strings.Time_PreciseDate_m11("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m11
|
||||
case 12:
|
||||
return strings.Time_PreciseDate_m12("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Time_PreciseDate_m12
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
return monthFormat(dayString, yearString, timeString).0
|
||||
}
|
||||
|
||||
public func stringForDate(timestamp: Int32, strings: PresentationStrings) -> String {
|
||||
|
@ -2,7 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "gramicon.pdf"
|
||||
"filename" : "ic_wallet.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
Binary file not shown.
@ -161,7 +161,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers
|
||||
|
||||
let resultsEnabled = (resultCount ?? 0) > 5
|
||||
let resultsEnabled = (resultCount ?? 0) > 0
|
||||
self.resultsButton.setTitle(resultsText ?? "", with: labelFont, with: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, for: .normal)
|
||||
self.resultsButton.isUserInteractionEnabled = resultsEnabled
|
||||
|
||||
|
Binary file not shown.
@ -136,6 +136,8 @@ final class WalletContextImpl: WalletContext {
|
||||
self.presentationData = WalletPresentationData(
|
||||
theme: WalletTheme(
|
||||
info: WalletInfoTheme(
|
||||
buttonBackgroundColor: UIColor(rgb: 0x32aafe),
|
||||
buttonTextColor: .white,
|
||||
incomingFundsTitleColor: theme.chatList.secretTitleColor,
|
||||
outgoingFundsTitleColor: theme.list.itemDestructiveColor
|
||||
), setup: WalletSetupTheme(
|
||||
|
@ -110,6 +110,7 @@ class ItemListController: ViewController, KeyShortcutResponder, PresentableContr
|
||||
|
||||
private var theme: WalletTheme
|
||||
private var strings: WalletStrings
|
||||
private var hasNavigationBarSeparator: Bool
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
@ -212,7 +213,7 @@ class ItemListController: ViewController, KeyShortcutResponder, PresentableContr
|
||||
var willDisappear: ((Bool) -> Void)?
|
||||
var didDisappear: ((Bool) -> Void)?
|
||||
|
||||
init<ItemGenerationArguments>(theme: WalletTheme, strings: WalletStrings, updatedPresentationData: Signal<(theme: WalletTheme, strings: WalletStrings), NoError>, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>?) {
|
||||
init<ItemGenerationArguments>(theme: WalletTheme, strings: WalletStrings, updatedPresentationData: Signal<(theme: WalletTheme, strings: WalletStrings), NoError>, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal<ItemListControllerTabBarItem, NoError>?, hasNavigationBarSeparator: Bool = true) {
|
||||
self.state = state
|
||||
|> map { controllerState, nodeStateAndArgument -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
return (controllerState, (nodeStateAndArgument.0, nodeStateAndArgument.1))
|
||||
@ -220,8 +221,16 @@ class ItemListController: ViewController, KeyShortcutResponder, PresentableContr
|
||||
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.hasNavigationBarSeparator = hasNavigationBarSeparator
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: theme.navigationBar, strings: NavigationBarStrings(back: strings.Wallet_Navigation_Back, close: strings.Wallet_Navigation_Close)))
|
||||
let navigationBarTheme: NavigationBarTheme
|
||||
if hasNavigationBarSeparator {
|
||||
navigationBarTheme = theme.navigationBar
|
||||
} else {
|
||||
navigationBarTheme = NavigationBarTheme(buttonColor: theme.navigationBar.buttonColor, disabledButtonColor: theme.navigationBar.disabledButtonColor, primaryTextColor: theme.navigationBar.primaryTextColor, backgroundColor: theme.list.itemBlocksBackgroundColor, separatorColor: theme.list.itemBlocksBackgroundColor, badgeBackgroundColor: theme.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.navigationBar.badgeStrokeColor, badgeTextColor: theme.navigationBar.badgeTextColor)
|
||||
}
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: strings.Wallet_Navigation_Back, close: strings.Wallet_Navigation_Close)))
|
||||
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
@ -381,7 +390,14 @@ class ItemListController: ViewController, KeyShortcutResponder, PresentableContr
|
||||
if strongSelf.theme !== controllerState.theme {
|
||||
strongSelf.theme = controllerState.theme
|
||||
|
||||
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: strongSelf.theme.navigationBar, strings: NavigationBarStrings(back: strongSelf.strings.Wallet_Navigation_Back, close: strongSelf.strings.Wallet_Navigation_Close)))
|
||||
let navigationBarTheme: NavigationBarTheme
|
||||
if strongSelf.hasNavigationBarSeparator {
|
||||
navigationBarTheme = strongSelf.theme.navigationBar
|
||||
} else {
|
||||
navigationBarTheme = NavigationBarTheme(buttonColor: strongSelf.theme.navigationBar.buttonColor, disabledButtonColor: strongSelf.theme.navigationBar.disabledButtonColor, primaryTextColor: strongSelf.theme.navigationBar.primaryTextColor, backgroundColor: strongSelf.theme.list.itemBlocksBackgroundColor, separatorColor: strongSelf.theme.list.itemBlocksBackgroundColor, badgeBackgroundColor: strongSelf.theme.navigationBar.badgeBackgroundColor, badgeStrokeColor: strongSelf.theme.navigationBar.badgeStrokeColor, badgeTextColor: strongSelf.theme.navigationBar.badgeTextColor)
|
||||
}
|
||||
|
||||
strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: strongSelf.strings.Wallet_Navigation_Back, close: strongSelf.strings.Wallet_Navigation_Close)))
|
||||
strongSelf.statusBar.statusBarStyle = strongSelf.theme.statusBarStyle
|
||||
|
||||
var items = strongSelf.navigationItem.rightBarButtonItems ?? []
|
||||
|
292
submodules/WalletUI/Sources/WalletAmountItem.swift
Normal file
292
submodules/WalletUI/Sources/WalletAmountItem.swift
Normal file
@ -0,0 +1,292 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import AnimatedStickerNode
|
||||
import SwiftSignalKit
|
||||
import AppBundle
|
||||
|
||||
class WalletAmountItem: ListViewItem, ItemListItem {
|
||||
let theme: WalletTheme
|
||||
let amount: String
|
||||
let sectionId: ItemListSectionId
|
||||
let textUpdated: (String) -> Void
|
||||
let shouldUpdateText: (String) -> Bool
|
||||
let processPaste: ((String) -> String)?
|
||||
let updatedFocus: ((Bool) -> Void)?
|
||||
let tag: ItemListItemTag?
|
||||
|
||||
init(theme: WalletTheme, amount: String, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil) {
|
||||
self.theme = theme
|
||||
self.amount = amount
|
||||
self.sectionId = sectionId
|
||||
self.textUpdated = textUpdated
|
||||
self.shouldUpdateText = shouldUpdateText
|
||||
self.processPaste = processPaste
|
||||
self.updatedFocus = updatedFocus
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
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 = WalletAmountItemNode()
|
||||
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? WalletAmountItemNode {
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let integralFont = Font.medium(48.0)
|
||||
private let fractionalFont = Font.medium(24.0)
|
||||
|
||||
class WalletAmountItemNode: ListViewItemNode, UITextFieldDelegate, ItemListItemNode, ItemListItemFocusableNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
||||
private let textNode: TextFieldNode
|
||||
private let iconNode: AnimatedStickerNode
|
||||
private let measureNode: TextNode
|
||||
|
||||
private var item: WalletAmountItem?
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
return self.item?.tag
|
||||
}
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.textNode = TextFieldNode()
|
||||
|
||||
self.iconNode = AnimatedStickerNode()
|
||||
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
|
||||
self.iconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct)
|
||||
self.iconNode.visibility = true
|
||||
}
|
||||
|
||||
self.measureNode = TextNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.clipsToBounds = false
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.textNode.textField.textAlignment = .center
|
||||
self.textNode.textField.typingAttributes = [NSAttributedString.Key.font: integralFont]
|
||||
self.textNode.textField.font = integralFont
|
||||
if let item = self.item {
|
||||
self.textNode.textField.textColor = item.theme.list.itemPrimaryTextColor
|
||||
self.textNode.textField.keyboardAppearance = item.theme.keyboardAppearance
|
||||
self.textNode.textField.tintColor = item.theme.list.itemAccentColor
|
||||
//self.textNode.textField.accessibilityHint = item.placeholder
|
||||
}
|
||||
self.textNode.clipsToBounds = true
|
||||
self.textNode.textField.delegate = self
|
||||
self.textNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged)
|
||||
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: WalletAmountItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
let makeMeasureLayout = TextNode.asyncLayout(self.measureNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: WalletTheme?
|
||||
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 16.0 + params.leftInset
|
||||
var rightInset: CGFloat = 16.0 + params.rightInset
|
||||
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: 100.0)
|
||||
var insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
insets.top = 0.0
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
let attributedPlaceholderText = NSAttributedString(string: "0", font: integralFont, textColor: item.theme.list.itemPlaceholderTextColor)
|
||||
let attributedAmountText = amountAttributedString(item.amount, integralFont: integralFont, fractionalFont: fractionalFont, color: item.theme.list.itemPrimaryTextColor)
|
||||
|
||||
let (measureLayout, _) = makeMeasureLayout(TextNodeLayoutArguments(attributedString: item.amount.isEmpty ? attributedPlaceholderText : attributedAmountText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
strongSelf.textNode.textField.textColor = item.theme.list.itemPrimaryTextColor
|
||||
strongSelf.textNode.textField.keyboardAppearance = item.theme.keyboardAppearance
|
||||
strongSelf.textNode.textField.tintColor = item.theme.list.itemAccentColor
|
||||
}
|
||||
|
||||
let capitalizationType = UITextAutocapitalizationType.none
|
||||
let autocorrectionType = UITextAutocorrectionType.no
|
||||
let keyboardType = UIKeyboardType.decimalPad
|
||||
|
||||
if strongSelf.textNode.textField.keyboardType != keyboardType {
|
||||
strongSelf.textNode.textField.keyboardType = keyboardType
|
||||
}
|
||||
if strongSelf.textNode.textField.autocapitalizationType != capitalizationType {
|
||||
strongSelf.textNode.textField.autocapitalizationType = capitalizationType
|
||||
}
|
||||
if strongSelf.textNode.textField.autocorrectionType != autocorrectionType {
|
||||
strongSelf.textNode.textField.autocorrectionType = autocorrectionType
|
||||
}
|
||||
|
||||
if let currentText = strongSelf.textNode.textField.text {
|
||||
if currentText != item.amount {
|
||||
strongSelf.textNode.textField.attributedText = attributedAmountText
|
||||
}
|
||||
} else {
|
||||
strongSelf.textNode.textField.attributedText = attributedAmountText
|
||||
}
|
||||
|
||||
let iconSize = CGSize(width: 50.0, height: 50.0)
|
||||
let verticalOffset: CGFloat = -10.0
|
||||
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - 48.0) / 2.0) + verticalOffset), size: CGSize(width: max(1.0, params.width - (leftInset + rightInset) + iconSize.width - 5.0), height: 48.0))
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1)
|
||||
}
|
||||
|
||||
let bottomStripeInset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
}
|
||||
|
||||
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.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
if strongSelf.textNode.textField.attributedPlaceholder == nil || !strongSelf.textNode.textField.attributedPlaceholder!.isEqual(to: attributedPlaceholderText) {
|
||||
strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText
|
||||
strongSelf.textNode.textField.accessibilityHint = attributedPlaceholderText.string
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - max(31.0, measureLayout.size.width)) / 2.0 - 28.0), y: floor((layout.contentSize.height - iconSize.height) / 2.0) - 3.0 + verticalOffset), size: iconSize)
|
||||
strongSelf.iconNode.updateLayout(size: iconFrame.size)
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
@objc private func textFieldTextChanged(_ textField: UITextField) {
|
||||
self.textUpdated(self.textNode.textField.text ?? "")
|
||||
}
|
||||
|
||||
@objc private func clearButtonPressed() {
|
||||
self.textNode.textField.text = ""
|
||||
self.textUpdated("")
|
||||
}
|
||||
|
||||
private func textUpdated(_ text: String) {
|
||||
self.item?.textUpdated(text)
|
||||
}
|
||||
|
||||
func focus() {
|
||||
if !self.textNode.textField.isFirstResponder {
|
||||
self.textNode.textField.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||
if let item = self.item {
|
||||
if !item.shouldUpdateText(newText) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if string.count > 1, let item = self.item, let processPaste = item.processPaste {
|
||||
let result = processPaste(string)
|
||||
if result != string {
|
||||
var text = textField.text ?? ""
|
||||
text.replaceSubrange(text.index(text.startIndex, offsetBy: range.lowerBound) ..< text.index(text.startIndex, offsetBy: range.upperBound), with: result)
|
||||
textField.attributedText = amountAttributedString(text, integralFont: integralFont, fractionalFont: fractionalFont, color: item.theme.list.itemPrimaryTextColor)
|
||||
if let startPosition = textField.position(from: textField.beginningOfDocument, offset: range.lowerBound + result.count) {
|
||||
let selectionRange = textField.textRange(from: startPosition, to: startPosition)
|
||||
DispatchQueue.main.async {
|
||||
textField.selectedTextRange = selectionRange
|
||||
}
|
||||
}
|
||||
self.textFieldTextChanged(textField)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let item = self.item {
|
||||
textField.attributedText = amountAttributedString(newText, integralFont: integralFont, fractionalFont: fractionalFont, color: item.theme.list.itemPrimaryTextColor)
|
||||
self.textFieldTextChanged(textField)
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@objc func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
self.item?.updatedFocus?(true)
|
||||
}
|
||||
|
||||
@objc func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
self.item?.updatedFocus?(false)
|
||||
}
|
||||
}
|
168
submodules/WalletUI/Sources/WalletBalanceItem.swift
Normal file
168
submodules/WalletUI/Sources/WalletBalanceItem.swift
Normal file
@ -0,0 +1,168 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Markdown
|
||||
|
||||
class WalletBalanceItem: ListViewItem, ItemListItem {
|
||||
let theme: WalletTheme
|
||||
let title: String
|
||||
let value: String
|
||||
let insufficient: Bool
|
||||
let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let isAlwaysPlain: Bool = true
|
||||
|
||||
init(theme: WalletTheme, title: String, value: String, insufficient: Bool, sectionId: ItemListSectionId, style: ItemListStyle = .blocks) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.insufficient = insufficient
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
}
|
||||
|
||||
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 = WalletBalanceItemNode()
|
||||
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 {
|
||||
guard let nodeValue = node() as? WalletBalanceItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.regular(14.0)
|
||||
private let transactionIcon = UIImage(bundleImageName: "Wallet/TransactionGem")?.precomposed()
|
||||
|
||||
class WalletBalanceItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
private let valueNode: TextNode
|
||||
private let iconNode: ASImageNode
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: WalletBalanceItem?
|
||||
|
||||
init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.valueNode = TextNode()
|
||||
self.valueNode.isUserInteractionEnabled = false
|
||||
self.valueNode.contentMode = .left
|
||||
self.valueNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
self.activateArea.accessibilityTraits = .staticText
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.valueNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: WalletBalanceItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeValueLayout = TextNode.asyncLayout(self.valueNode)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: WalletTheme?
|
||||
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||
let verticalInset: CGFloat = 7.0
|
||||
|
||||
let iconImage: UIImage? = transactionIcon
|
||||
let iconSize = CGSize(width: 12.0, height: 10.0)
|
||||
|
||||
let textColor = item.insufficient ? item.theme.list.freeTextErrorColor : item.theme.list.freeTextColor
|
||||
|
||||
let attributedTitle = NSAttributedString(string: item.title, font: titleFont, textColor: textColor)
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let attributedValue = NSAttributedString(string: item.value, font: titleFont, textColor: textColor)
|
||||
let (valueLayout, valueApply) = makeValueLayout(TextNodeLayoutArguments(attributedString: attributedValue, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize: CGSize
|
||||
|
||||
contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset + verticalInset)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [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))
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.iconNode.image = iconImage
|
||||
}
|
||||
|
||||
let accessibilityLabel = item.title + item.value
|
||||
strongSelf.activateArea.accessibilityLabel = accessibilityLabel
|
||||
|
||||
strongSelf.accessibilityLabel = accessibilityLabel
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = valueApply()
|
||||
|
||||
let iconSpacing: CGFloat = 3.0
|
||||
let valueSpacing: CGFloat = 2.0
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
|
||||
strongSelf.valueNode.frame = CGRect(origin: CGPoint(x: leftInset + titleLayout.size.width + iconSpacing + iconSize.width + valueSpacing, y: verticalInset), size: valueLayout.size)
|
||||
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: leftInset + titleLayout.size.width + iconSpacing, y: verticalInset + 3.0), size: iconSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -10,24 +10,14 @@ private final class WalletCreateInvoiceScreenArguments {
|
||||
let context: WalletContext
|
||||
let updateState: ((WalletCreateInvoiceScreenState) -> WalletCreateInvoiceScreenState) -> Void
|
||||
let updateText: (WalletCreateInvoiceScreenEntryTag, String) -> Void
|
||||
let selectNextInputItem: (WalletCreateInvoiceScreenEntryTag) -> Void
|
||||
let dismissInput: () -> Void
|
||||
let copyAddress: () -> Void
|
||||
let shareAddressLink: () -> Void
|
||||
let openQrCode: () -> Void
|
||||
let displayQrCodeContextMenu: () -> Void
|
||||
let scrollToBottom: () -> Void
|
||||
|
||||
init(context: WalletContext, updateState: @escaping ((WalletCreateInvoiceScreenState) -> WalletCreateInvoiceScreenState) -> Void, updateText: @escaping (WalletCreateInvoiceScreenEntryTag, String) -> Void, selectNextInputItem: @escaping (WalletCreateInvoiceScreenEntryTag) -> Void, dismissInput: @escaping () -> Void, copyAddress: @escaping () -> Void, shareAddressLink: @escaping () -> Void, openQrCode: @escaping () -> Void, displayQrCodeContextMenu: @escaping () -> Void, scrollToBottom: @escaping () -> Void) {
|
||||
init(context: WalletContext, updateState: @escaping ((WalletCreateInvoiceScreenState) -> WalletCreateInvoiceScreenState) -> Void, updateText: @escaping (WalletCreateInvoiceScreenEntryTag, String) -> Void, dismissInput: @escaping () -> Void, scrollToBottom: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
self.updateText = updateText
|
||||
self.selectNextInputItem = selectNextInputItem
|
||||
self.dismissInput = dismissInput
|
||||
self.copyAddress = copyAddress
|
||||
self.shareAddressLink = shareAddressLink
|
||||
self.openQrCode = openQrCode
|
||||
self.displayQrCodeContextMenu = displayQrCodeContextMenu
|
||||
self.scrollToBottom = scrollToBottom
|
||||
}
|
||||
}
|
||||
@ -35,7 +25,6 @@ private final class WalletCreateInvoiceScreenArguments {
|
||||
private enum WalletCreateInvoiceScreenSection: Int32 {
|
||||
case amount
|
||||
case comment
|
||||
case address
|
||||
}
|
||||
|
||||
private enum WalletCreateInvoiceScreenEntryTag: ItemListItemTag {
|
||||
@ -52,63 +41,43 @@ private enum WalletCreateInvoiceScreenEntryTag: ItemListItemTag {
|
||||
}
|
||||
|
||||
private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
|
||||
case amountHeader(WalletTheme, String)
|
||||
case amount(WalletTheme, WalletStrings, String, String)
|
||||
case amount(WalletTheme, String)
|
||||
case amountInfo(WalletTheme, String)
|
||||
case commentHeader(WalletTheme, String)
|
||||
case comment(WalletTheme, String, String)
|
||||
case addressCode(WalletTheme, String)
|
||||
case addressHeader(WalletTheme, String)
|
||||
case address(WalletTheme, String, Bool)
|
||||
case copyAddress(WalletTheme, String)
|
||||
case shareAddressLink(WalletTheme, String)
|
||||
case addressInfo(WalletTheme, String)
|
||||
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .amountHeader, .amount:
|
||||
case .amount, .amountInfo:
|
||||
return WalletCreateInvoiceScreenSection.amount.rawValue
|
||||
case .commentHeader, .comment:
|
||||
return WalletCreateInvoiceScreenSection.comment.rawValue
|
||||
case .addressCode, .addressHeader, .address, .copyAddress, .shareAddressLink, .addressInfo:
|
||||
return WalletCreateInvoiceScreenSection.address.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .amountHeader:
|
||||
return 0
|
||||
case .amount:
|
||||
return 0
|
||||
case .amountInfo:
|
||||
return 1
|
||||
case .commentHeader:
|
||||
return 2
|
||||
case .comment:
|
||||
return 3
|
||||
case .addressCode:
|
||||
return 4
|
||||
case .addressHeader:
|
||||
return 5
|
||||
case .address:
|
||||
return 6
|
||||
case .copyAddress:
|
||||
return 7
|
||||
case .shareAddressLink:
|
||||
return 8
|
||||
case .addressInfo:
|
||||
return 9
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: WalletCreateInvoiceScreenEntry, rhs: WalletCreateInvoiceScreenEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .amountHeader(lhsTheme, lhsText):
|
||||
if case let .amountHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
case let .amount(lhsTheme, lhsAmount):
|
||||
if case let .amount(rhsTheme, rhsAmount) = rhs, lhsTheme === rhsTheme, lhsAmount == rhsAmount {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .amount(lhsTheme, lhsStrings, lhsPlaceholder, lhsBalance):
|
||||
if case let .amount(rhsTheme, rhsStrings, rhsPlaceholder, rhsBalance) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsBalance == rhsBalance {
|
||||
case let .amountInfo(lhsTheme, lhsText):
|
||||
if case let .amountInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -125,42 +94,6 @@ private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addressCode(lhsTheme, lhsAddress):
|
||||
if case let .addressCode(rhsTheme, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsAddress == rhsAddress {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addressHeader(lhsTheme, lhsText):
|
||||
if case let .addressHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .address(lhsTheme, lhsAddress, lhsMonospace):
|
||||
if case let .address(rhsTheme, rhsAddress, rhsMonospace) = rhs, lhsTheme === rhsTheme, lhsAddress == rhsAddress, lhsMonospace == rhsMonospace {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .copyAddress(lhsTheme, lhsText):
|
||||
if case let .copyAddress(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .shareAddressLink(lhsTheme, lhsText):
|
||||
if case let .shareAddressLink(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addressInfo(lhsTheme, lhsText):
|
||||
if case let .addressInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,10 +104,8 @@ private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! WalletCreateInvoiceScreenArguments
|
||||
switch self {
|
||||
case let .amountHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .amount(theme, strings, placeholder, text):
|
||||
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: placeholder, type: .decimal, returnKeyType: .next, clearType: .onFocus, tag: WalletCreateInvoiceScreenEntryTag.amount, sectionId: self.section, textUpdated: { text in
|
||||
case let .amount(theme, amount):
|
||||
return WalletAmountItem(theme: theme, amount: amount, sectionId: self.section, textUpdated: { text in
|
||||
let text = formatAmountText(text, decimalSeparator: arguments.context.presentationData.dateTimeFormat.decimalSeparator)
|
||||
arguments.updateText(WalletCreateInvoiceScreenEntryTag.amount, text)
|
||||
}, shouldUpdateText: { text in
|
||||
@ -183,7 +114,7 @@ private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
|
||||
if isValidAmount(pastedText) {
|
||||
return normalizedStringForGramsString(pastedText)
|
||||
} else {
|
||||
return text
|
||||
return amount
|
||||
}
|
||||
}, updatedFocus: { focus in
|
||||
arguments.updateState { state in
|
||||
@ -191,9 +122,7 @@ private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
|
||||
state.focusItemTag = focus ? WalletCreateInvoiceScreenEntryTag.amount : nil
|
||||
return state
|
||||
}
|
||||
if focus {
|
||||
arguments.scrollToBottom()
|
||||
} else {
|
||||
if !focus {
|
||||
let presentationData = arguments.context.presentationData
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
@ -203,13 +132,13 @@ private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}, action: {
|
||||
arguments.selectNextInputItem(WalletCreateInvoiceScreenEntryTag.amount)
|
||||
})
|
||||
}, tag: WalletCreateInvoiceScreenEntryTag.amount)
|
||||
case let .amountInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
|
||||
case let .commentHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .comment(theme, placeholder, value):
|
||||
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: walletTextLimit, display: true), sectionId: self.section, style: .blocks, returnKeyType: .done, textUpdated: { text in
|
||||
return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: walletTextLimit, display: true, mode: .bytes), sectionId: self.section, style: .blocks, returnKeyType: .done, textUpdated: { text in
|
||||
arguments.updateText(WalletCreateInvoiceScreenEntryTag.comment, text)
|
||||
}, shouldUpdateText: { text in
|
||||
let textLength: Int = text.data(using: .utf8, allowLossyConversion: true)?.count ?? 0
|
||||
@ -226,26 +155,6 @@ private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
|
||||
}, tag: WalletCreateInvoiceScreenEntryTag.comment, action: {
|
||||
arguments.dismissInput()
|
||||
})
|
||||
case let .addressCode(theme, text):
|
||||
return WalletQrCodeItem(theme: theme, address: text, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openQrCode()
|
||||
}, longTapAction: {
|
||||
arguments.displayQrCodeContextMenu()
|
||||
})
|
||||
case let .addressHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .address(theme, text, monospace):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, font: monospace ? .monospace : .default, sectionId: self.section, style: .blocks)
|
||||
case let .copyAddress(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.copyAddress()
|
||||
})
|
||||
case let .shareAddressLink(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.shareAddressLink()
|
||||
})
|
||||
case let .addressInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,23 +171,10 @@ private struct WalletCreateInvoiceScreenState: Equatable {
|
||||
|
||||
private func walletCreateInvoiceScreenEntries(presentationData: WalletPresentationData, address: String, state: WalletCreateInvoiceScreenState) -> [WalletCreateInvoiceScreenEntry] {
|
||||
var entries: [WalletCreateInvoiceScreenEntry] = []
|
||||
|
||||
let amount = amountValue(state.amount)
|
||||
entries.append(.amountHeader(presentationData.theme, presentationData.strings.Wallet_Receive_AmountHeader))
|
||||
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.strings.Wallet_Receive_AmountText, state.amount ?? ""))
|
||||
|
||||
entries.append(.amount(presentationData.theme, state.amount ?? ""))
|
||||
entries.append(.amountInfo(presentationData.theme, presentationData.strings.Wallet_Receive_CreateInvoiceInfo))
|
||||
entries.append(.commentHeader(presentationData.theme, presentationData.strings.Wallet_Receive_CommentHeader))
|
||||
entries.append(.comment(presentationData.theme, presentationData.strings.Wallet_Receive_CommentInfo, state.comment))
|
||||
|
||||
let url = walletInvoiceUrl(address: address, amount: state.amount, comment: state.comment)
|
||||
entries.append(.addressCode(presentationData.theme, url))
|
||||
entries.append(.addressHeader(presentationData.theme, presentationData.strings.Wallet_Receive_InvoiceUrlHeader))
|
||||
|
||||
entries.append(.address(presentationData.theme, url, false))
|
||||
entries.append(.copyAddress(presentationData.theme, presentationData.strings.Wallet_Receive_CopyInvoiceUrl))
|
||||
entries.append(.shareAddressLink(presentationData.theme, presentationData.strings.Wallet_Receive_ShareInvoiceUrl))
|
||||
entries.append(.addressInfo(presentationData.theme, presentationData.strings.Wallet_Receive_ShareUrlInfo))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -287,6 +183,9 @@ protocol WalletCreateInvoiceScreen {
|
||||
}
|
||||
|
||||
private final class WalletCreateInvoiceScreenImpl: ItemListController, WalletCreateInvoiceScreen {
|
||||
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
return CGSize(width: layout.size.width, height: layout.size.height - 174.0)
|
||||
}
|
||||
}
|
||||
|
||||
func walletCreateInvoiceScreen(context: WalletContext, address: String) -> ViewController {
|
||||
@ -300,10 +199,8 @@ func walletCreateInvoiceScreen(context: WalletContext, address: String) -> ViewC
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var selectNextInputItemImpl: ((WalletCreateInvoiceScreenEntryTag) -> Void)?
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
var ensureItemVisibleImpl: ((WalletCreateInvoiceScreenEntryTag, Bool) -> Void)?
|
||||
var displayQrCodeContextMenuImpl: (() -> Void)?
|
||||
|
||||
weak var currentStatusController: ViewController?
|
||||
let arguments = WalletCreateInvoiceScreenArguments(context: context, updateState: { f in
|
||||
@ -319,59 +216,40 @@ func walletCreateInvoiceScreen(context: WalletContext, address: String) -> ViewC
|
||||
}
|
||||
return state
|
||||
}
|
||||
ensureItemVisibleImpl?(WalletCreateInvoiceScreenEntryTag.comment, false)
|
||||
}, selectNextInputItem: { tag in
|
||||
selectNextInputItemImpl?(tag)
|
||||
ensureItemVisibleImpl?(tag, false)
|
||||
}, dismissInput: {
|
||||
dismissInputImpl?()
|
||||
}, copyAddress: {
|
||||
let presentationData = context.presentationData
|
||||
let state = stateValue.with { $0 }
|
||||
|
||||
UIPasteboard.general.string = walletInvoiceUrl(address: address, amount: state.amount, comment: state.comment)
|
||||
|
||||
if currentStatusController == nil {
|
||||
let statusController = OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Wallet_Receive_InvoiceUrlCopied, false))
|
||||
presentControllerImpl?(statusController, nil)
|
||||
currentStatusController = statusController
|
||||
}
|
||||
}, shareAddressLink: {
|
||||
dismissInputImpl?()
|
||||
let state = stateValue.with { $0 }
|
||||
let url = walletInvoiceUrl(address: address, amount: state.amount, comment: state.comment)
|
||||
context.shareUrl(url)
|
||||
}, openQrCode: {
|
||||
dismissInputImpl?()
|
||||
let state = stateValue.with { $0 }
|
||||
let url = walletInvoiceUrl(address: address, amount: state.amount, comment: state.comment)
|
||||
pushImpl?(WalletQrViewScreen(context: context, invoice: url))
|
||||
}, displayQrCodeContextMenu: {
|
||||
dismissInputImpl?()
|
||||
displayQrCodeContextMenuImpl?()
|
||||
}, scrollToBottom: {
|
||||
ensureItemVisibleImpl?(WalletCreateInvoiceScreenEntryTag.comment, true)
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var ensureVisibleItemTag: ItemListItemTag?
|
||||
if let focusItemTag = state.focusItemTag {
|
||||
ensureVisibleItemTag = focusItemTag
|
||||
}
|
||||
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Navigation_Done), style: .bold, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_CreateInvoice_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletCreateInvoiceScreenEntries(presentationData: presentationData, address: address, state: state), style: .blocks, ensureVisibleItemTag: ensureVisibleItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var ensureVisibleItemTag: ItemListItemTag?
|
||||
if let focusItemTag = state.focusItemTag {
|
||||
ensureVisibleItemTag = focusItemTag
|
||||
}
|
||||
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Navigation_Done), style: .bold, enabled: !state.isEmpty, action: {
|
||||
pushImpl?(WalletReceiveScreen(context: context, mode: .invoice(address: address, amount: state.amount, comment: state.comment)))
|
||||
})
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_CreateInvoice_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletCreateInvoiceScreenEntries(presentationData: presentationData, address: address, state: state), style: .blocks, focusItemTag: ensureVisibleItemTag, ensureVisibleItemTag: ensureVisibleItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
let controller = WalletCreateInvoiceScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil)
|
||||
let controller = WalletCreateInvoiceScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil, hasNavigationBarSeparator: false)
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
controller.experimentalSnapScrollToItem = true
|
||||
controller.didAppear = { _ in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.focusItemTag = .amount
|
||||
return state
|
||||
}
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
@ -382,27 +260,6 @@ func walletCreateInvoiceScreen(context: WalletContext, address: String) -> ViewC
|
||||
controller?.view.endEditing(true)
|
||||
let _ = controller?.dismiss()
|
||||
}
|
||||
selectNextInputItemImpl = { [weak controller] currentTag in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
var resultItemNode: ItemListItemFocusableNode?
|
||||
var focusOnNext = false
|
||||
let _ = controller.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode, let tag = itemNode.tag, let focusableItemNode = itemNode as? ItemListItemFocusableNode {
|
||||
if focusOnNext && resultItemNode == nil {
|
||||
resultItemNode = focusableItemNode
|
||||
return true
|
||||
} else if currentTag.isEqual(to: tag) {
|
||||
focusOnNext = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode {
|
||||
resultItemNode.focus()
|
||||
}
|
||||
}
|
||||
dismissInputImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
}
|
||||
@ -427,10 +284,5 @@ func walletCreateInvoiceScreen(context: WalletContext, address: String) -> ViewC
|
||||
}
|
||||
})
|
||||
}
|
||||
displayQrCodeContextMenuImpl = { [weak controller] in
|
||||
let state = stateValue.with { $0 }
|
||||
let url = walletInvoiceUrl(address: address, amount: state.amount, comment: state.comment)
|
||||
shareInvoiceQrCode(context: context, invoice: url)
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
@ -9,6 +9,39 @@ import MergeLists
|
||||
import AnimatedStickerNode
|
||||
import WalletCore
|
||||
|
||||
private class WalletInfoTitleView: UIView, NavigationBarTitleView {
|
||||
private let buttonView: HighlightTrackingButton
|
||||
private let action: () -> Void
|
||||
|
||||
init(action: @escaping () -> Void) {
|
||||
self.action = action
|
||||
|
||||
self.buttonView = HighlightTrackingButton()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.buttonView)
|
||||
|
||||
self.buttonView.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.action()
|
||||
}
|
||||
|
||||
func animateLayoutTransition() {
|
||||
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
|
||||
self.buttonView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
||||
}
|
||||
}
|
||||
|
||||
public final class WalletInfoScreen: ViewController {
|
||||
private let context: WalletContext
|
||||
private let walletInfo: WalletInfo?
|
||||
@ -44,6 +77,8 @@ public final class WalletInfoScreen: ViewController {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Wallet/NavigationSettingsIcon"), color: .white), style: .plain, target: self, action: #selector(self.settingsPressed))
|
||||
}
|
||||
|
||||
self.navigationItem.titleView = WalletInfoTitleView(action: { [weak self] in self?.scrollToTop?() })
|
||||
|
||||
self.scrollToTop = { [weak self] in
|
||||
(self?.displayNode as? WalletInfoScreenNode)?.scrollToTop()
|
||||
}
|
||||
@ -75,12 +110,12 @@ public final class WalletInfoScreen: ViewController {
|
||||
guard let strongSelf = self, let walletInfo = strongSelf.walletInfo else {
|
||||
return
|
||||
}
|
||||
strongSelf.push(walletReceiveScreen(context: strongSelf.context, address: strongSelf.address))
|
||||
strongSelf.push(WalletReceiveScreen(context: strongSelf.context, mode: .receive(address: strongSelf.address)))
|
||||
}, openTransaction: { [weak self] transaction in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.push(walletTransactionInfoController(context: strongSelf.context, walletInfo: strongSelf.walletInfo, walletTransaction: transaction, enableDebugActions: strongSelf.enableDebugActions))
|
||||
strongSelf.push(WalletTransactionInfoScreen(context: strongSelf.context, walletInfo: strongSelf.walletInfo, walletTransaction: transaction, enableDebugActions: strongSelf.enableDebugActions))
|
||||
}, present: { [weak self] c, a in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -229,9 +264,9 @@ private final class WalletInfoHeaderNode: ASDisplayNode {
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 20.0, height: 20.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 1)
|
||||
|
||||
self.receiveButtonNode = SolidRoundedButtonNode(title: presentationData.strings.Wallet_Info_Receive, icon: UIImage(bundleImageName: "Wallet/ReceiveButtonIcon"), theme: SolidRoundedButtonTheme(backgroundColor: .white, foregroundColor: .black), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.receiveGramsButtonNode = SolidRoundedButtonNode(title: presentationData.strings.Wallet_Info_ReceiveGrams, icon: UIImage(bundleImageName: "Wallet/ReceiveButtonIcon"), theme: SolidRoundedButtonTheme(backgroundColor: .white, foregroundColor: .black), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.sendButtonNode = SolidRoundedButtonNode(title: presentationData.strings.Wallet_Info_Send, icon: UIImage(bundleImageName: "Wallet/SendButtonIcon"), theme: SolidRoundedButtonTheme(backgroundColor: .white, foregroundColor: .black), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.receiveButtonNode = SolidRoundedButtonNode(title: presentationData.strings.Wallet_Info_Receive, icon: generateTintedImage(image: UIImage(bundleImageName: "Wallet/ReceiveButtonIcon"), color: presentationData.theme.info.buttonTextColor), theme: SolidRoundedButtonTheme(backgroundColor: presentationData.theme.info.buttonBackgroundColor, foregroundColor: presentationData.theme.info.buttonTextColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.receiveGramsButtonNode = SolidRoundedButtonNode(title: presentationData.strings.Wallet_Info_ReceiveGrams, icon: generateTintedImage(image: UIImage(bundleImageName: "Wallet/ReceiveButtonIcon"), color: presentationData.theme.info.buttonTextColor), theme: SolidRoundedButtonTheme(backgroundColor: presentationData.theme.info.buttonBackgroundColor, foregroundColor: presentationData.theme.info.buttonTextColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.sendButtonNode = SolidRoundedButtonNode(title: presentationData.strings.Wallet_Info_Send, icon: generateTintedImage(image: UIImage(bundleImageName: "Wallet/SendButtonIcon"), color: presentationData.theme.info.buttonTextColor), theme: SolidRoundedButtonTheme(backgroundColor: presentationData.theme.info.buttonBackgroundColor, foregroundColor: presentationData.theme.info.buttonTextColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
|
||||
self.refreshNode = WalletRefreshNode(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
|
||||
|
@ -376,7 +376,7 @@ class WalletInfoTransactionItemNode: ListViewItemNode {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: titleSignFrame.maxX + (titleSignFrame.width.isZero ? 0.0 : 1.0), y: titleSignFrame.minY + floor((titleLayout.size.height - iconSize.height) / 2.0) - 1.0), size: iconSize)
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + 1.0, y: topInset), size: titleLayout.size)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + 3.0, y: topInset), size: titleLayout.size)
|
||||
strongSelf.titleNode.frame = titleFrame
|
||||
|
||||
let directionFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 3.0, y: titleFrame.maxY - directionLayout.size.height - 1.0), size: directionLayout.size)
|
||||
|
@ -31,13 +31,19 @@ public struct WalletPresentationDateTimeFormat: Equatable {
|
||||
}
|
||||
|
||||
public final class WalletInfoTheme {
|
||||
public let buttonBackgroundColor: UIColor
|
||||
public let buttonTextColor: UIColor
|
||||
public let incomingFundsTitleColor: UIColor
|
||||
public let outgoingFundsTitleColor: UIColor
|
||||
|
||||
public init(
|
||||
buttonBackgroundColor: UIColor,
|
||||
buttonTextColor: UIColor,
|
||||
incomingFundsTitleColor: UIColor,
|
||||
outgoingFundsTitleColor: UIColor
|
||||
) {
|
||||
self.buttonBackgroundColor = buttonBackgroundColor
|
||||
self.buttonTextColor = buttonTextColor
|
||||
self.incomingFundsTitleColor = incomingFundsTitleColor
|
||||
self.outgoingFundsTitleColor = outgoingFundsTitleColor
|
||||
}
|
||||
|
@ -7,23 +7,6 @@ import Display
|
||||
import QrCode
|
||||
import AnimatedStickerNode
|
||||
|
||||
func shareInvoiceQrCode(context: WalletContext, invoice: String) {
|
||||
let _ = (qrCode(string: invoice, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Wallet/QrGem")))
|
||||
|> map { generator -> UIImage? in
|
||||
let imageSize = CGSize(width: 768.0, height: 768.0)
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||
return context?.generateImage()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { image in
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
||||
context.presentNativeController(activityController)
|
||||
})
|
||||
}
|
||||
|
||||
public final class WalletQrViewScreen: ViewController {
|
||||
private let context: WalletContext
|
||||
private let invoice: String
|
||||
@ -104,7 +87,7 @@ public final class WalletQrViewScreen: ViewController {
|
||||
}
|
||||
|
||||
@objc private func shareButtonPressed() {
|
||||
shareInvoiceQrCode(context: self.context, invoice: self.invoice)
|
||||
//shareInvoiceQrCode(context: self.context, invoice: self.invoice)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,245 +1,314 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import AppBundle
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import OverlayStatusController
|
||||
import QrCode
|
||||
import AnimatedStickerNode
|
||||
import SolidRoundedButtonNode
|
||||
|
||||
private final class WalletReceiveScreenArguments {
|
||||
let context: WalletContext
|
||||
let copyAddress: () -> Void
|
||||
let shareAddressLink: () -> Void
|
||||
let openQrCode: () -> Void
|
||||
let displayQrCodeContextMenu: () -> Void
|
||||
let openCreateInvoice: () -> Void
|
||||
|
||||
init(context: WalletContext, copyAddress: @escaping () -> Void, shareAddressLink: @escaping () -> Void, openQrCode: @escaping () -> Void, displayQrCodeContextMenu: @escaping () -> Void, openCreateInvoice: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.copyAddress = copyAddress
|
||||
self.shareAddressLink = shareAddressLink
|
||||
self.openQrCode = openQrCode
|
||||
self.displayQrCodeContextMenu = displayQrCodeContextMenu
|
||||
self.openCreateInvoice = openCreateInvoice
|
||||
private func shareInvoiceQrCode(context: WalletContext, invoice: String) {
|
||||
let _ = (qrCode(string: invoice, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Wallet/QrGem")))
|
||||
|> map { generator -> UIImage? in
|
||||
let imageSize = CGSize(width: 768.0, height: 768.0)
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||
return context?.generateImage()
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletReceiveScreenSection: Int32 {
|
||||
case address
|
||||
case invoice
|
||||
}
|
||||
|
||||
private enum WalletReceiveScreenEntry: ItemListNodeEntry {
|
||||
case addressCode(WalletTheme, String)
|
||||
case addressHeader(WalletTheme, String)
|
||||
case address(WalletTheme, String, Bool)
|
||||
case copyAddress(WalletTheme, String)
|
||||
case shareAddressLink(WalletTheme, String)
|
||||
case addressInfo(WalletTheme, String)
|
||||
case invoice(WalletTheme, String)
|
||||
case invoiceInfo(WalletTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .addressCode, .addressHeader, .address, .copyAddress, .shareAddressLink, .addressInfo:
|
||||
return WalletReceiveScreenSection.address.rawValue
|
||||
case .invoice, .invoiceInfo:
|
||||
return WalletReceiveScreenSection.invoice.rawValue
|
||||
|> deliverOnMainQueue).start(next: { image in
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .addressCode:
|
||||
return 0
|
||||
case .addressHeader:
|
||||
return 1
|
||||
case .address:
|
||||
return 2
|
||||
case .copyAddress:
|
||||
return 3
|
||||
case .shareAddressLink:
|
||||
return 4
|
||||
case .addressInfo:
|
||||
return 5
|
||||
case .invoice:
|
||||
return 6
|
||||
case .invoiceInfo:
|
||||
return 7
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: WalletReceiveScreenEntry, rhs: WalletReceiveScreenEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .addressCode(lhsTheme, lhsAddress):
|
||||
if case let .addressCode(rhsTheme, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsAddress == rhsAddress {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addressHeader(lhsTheme, lhsText):
|
||||
if case let .addressHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .address(lhsTheme, lhsAddress, lhsMonospace):
|
||||
if case let .address(rhsTheme, rhsAddress, rhsMonospace) = rhs, lhsTheme === rhsTheme, lhsAddress == rhsAddress, lhsMonospace == rhsMonospace {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .copyAddress(lhsTheme, lhsText):
|
||||
if case let .copyAddress(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .shareAddressLink(lhsTheme, lhsText):
|
||||
if case let .shareAddressLink(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addressInfo(lhsTheme, lhsText):
|
||||
if case let .addressInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .invoice(lhsTheme, lhsText):
|
||||
if case let .invoice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .invoiceInfo(lhsTheme, lhsText):
|
||||
if case let .invoiceInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: WalletReceiveScreenEntry, rhs: WalletReceiveScreenEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(_ arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! WalletReceiveScreenArguments
|
||||
switch self {
|
||||
case let .addressCode(theme, text):
|
||||
return WalletQrCodeItem(theme: theme, address: text, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openQrCode()
|
||||
}, longTapAction: {
|
||||
arguments.displayQrCodeContextMenu()
|
||||
})
|
||||
case let .addressHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .address(theme, text, monospace):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, font: monospace ? .monospace : .default, sectionId: self.section, style: .blocks)
|
||||
case let .copyAddress(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.copyAddress()
|
||||
})
|
||||
case let .shareAddressLink(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.shareAddressLink()
|
||||
})
|
||||
case let .addressInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
|
||||
case let .invoice(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openCreateInvoice()
|
||||
})
|
||||
case let .invoiceInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func walletReceiveScreenEntries(presentationData: WalletPresentationData, address: String) -> [WalletReceiveScreenEntry] {
|
||||
var entries: [WalletReceiveScreenEntry] = []
|
||||
entries.append(.addressCode(presentationData.theme, walletInvoiceUrl(address: address)))
|
||||
entries.append(.addressHeader(presentationData.theme, presentationData.strings.Wallet_Receive_AddressHeader))
|
||||
|
||||
entries.append(.address(presentationData.theme, formatAddress(address), true))
|
||||
entries.append(.copyAddress(presentationData.theme, presentationData.strings.Wallet_Receive_CopyAddress))
|
||||
entries.append(.shareAddressLink(presentationData.theme, presentationData.strings.Wallet_Receive_ShareAddress))
|
||||
entries.append(.addressInfo(presentationData.theme, presentationData.strings.Wallet_Receive_ShareUrlInfo))
|
||||
|
||||
entries.append(.invoice(presentationData.theme, presentationData.strings.Wallet_Receive_CreateInvoice))
|
||||
entries.append(.invoiceInfo(presentationData.theme, presentationData.strings.Wallet_Receive_CreateInvoiceInfo))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
protocol WalletReceiveScreen {
|
||||
|
||||
}
|
||||
|
||||
private final class WalletReceiveScreenImpl: ItemListController, WalletReceiveScreen {
|
||||
|
||||
}
|
||||
|
||||
func walletReceiveScreen(context: WalletContext, address: String) -> ViewController {
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var displayQrCodeContextMenuImpl: (() -> Void)?
|
||||
|
||||
weak var currentStatusController: ViewController?
|
||||
let arguments = WalletReceiveScreenArguments(context: context, copyAddress: {
|
||||
let presentationData = context.presentationData
|
||||
|
||||
UIPasteboard.general.string = address
|
||||
|
||||
if currentStatusController == nil {
|
||||
let statusController = OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Wallet_Receive_AddressCopied, false))
|
||||
presentControllerImpl?(statusController, nil)
|
||||
currentStatusController = statusController
|
||||
}
|
||||
}, shareAddressLink: {
|
||||
context.shareUrl(walletInvoiceUrl(address: address))
|
||||
}, openQrCode: {
|
||||
let url = walletInvoiceUrl(address: address)
|
||||
pushImpl?(WalletQrViewScreen(context: context, invoice: url))
|
||||
}, displayQrCodeContextMenu: {
|
||||
displayQrCodeContextMenuImpl?()
|
||||
}, openCreateInvoice: {
|
||||
pushImpl?(walletCreateInvoiceScreen(context: context, address: address))
|
||||
let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
||||
context.presentNativeController(activityController)
|
||||
})
|
||||
|
||||
let signal = Signal<WalletPresentationData, NoError>.single(context.presentationData)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Navigation_Done), style: .bold, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_Receive_Title), leftNavigationButton: ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}), rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletReceiveScreenEntries(presentationData: presentationData, address: address), style: .blocks, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
let controller = WalletReceiveScreenImpl(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)
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
pushImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
let _ = controller?.dismiss()
|
||||
}
|
||||
displayQrCodeContextMenuImpl = { [weak controller] in
|
||||
let url = walletInvoiceUrl(address: address)
|
||||
shareInvoiceQrCode(context: context, invoice: url)
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
public enum WalletReceiveScreenMode {
|
||||
case receive(address: String)
|
||||
case invoice(address: String, amount: String?, comment: String?)
|
||||
|
||||
var address: String {
|
||||
switch self {
|
||||
case let .receive(address), let .invoice(address, _, _):
|
||||
return address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class WalletReceiveScreen: ViewController {
|
||||
private let context: WalletContext
|
||||
private let mode: WalletReceiveScreenMode
|
||||
private var presentationData: WalletPresentationData
|
||||
|
||||
private var previousScreenBrightness: CGFloat?
|
||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
||||
private let idleTimerExtensionDisposable: Disposable
|
||||
|
||||
public init(context: WalletContext, mode: WalletReceiveScreenMode) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
|
||||
self.presentationData = context.presentationData
|
||||
|
||||
let defaultTheme = self.presentationData.theme.navigationBar
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
|
||||
|
||||
self.idleTimerExtensionDisposable = context.idleTimerExtension()
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Wallet_Navigation_Back, close: self.presentationData.strings.Wallet_Navigation_Close)))
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||
|
||||
if case .receive = mode {
|
||||
self.navigationPresentation = .flatModal
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
|
||||
}
|
||||
|
||||
self.title = self.presentationData.strings.Wallet_Receive_Title
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Back, style: .plain, target: nil, action: nil)
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.idleTimerExtensionDisposable.dispose()
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = WalletReceiveScreenNode(context: self.context, presentationData: self.presentationData, mode: self.mode)
|
||||
(self.displayNode as! WalletReceiveScreenNode).openCreateInvoice = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
self?.push(walletCreateInvoiceScreen(context: strongSelf.context, address: strongSelf.mode.address))
|
||||
}
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
let screenBrightness = UIScreen.main.brightness
|
||||
if screenBrightness < 0.85 {
|
||||
self.previousScreenBrightness = screenBrightness
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.5, from: screenBrightness, to: 0.85, update: { value in
|
||||
UIScreen.main.brightness = value
|
||||
}, completion: {
|
||||
self.displayLinkAnimator = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
let screenBrightness = UIScreen.main.brightness
|
||||
if let previousScreenBrightness = self.previousScreenBrightness, screenBrightness > previousScreenBrightness {
|
||||
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2, from: screenBrightness, to: previousScreenBrightness, update: { value in
|
||||
UIScreen.main.brightness = value
|
||||
}, completion: {
|
||||
self.displayLinkAnimator = nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
return CGSize(width: layout.size.width, height: layout.size.height - 174.0)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! WalletReceiveScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
@objc private func donePressed() {
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { controller in
|
||||
if controller is WalletReceiveScreen {
|
||||
return false
|
||||
}
|
||||
if controller is WalletCreateInvoiceScreen {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func urlForMode(_ mode: WalletReceiveScreenMode) -> String {
|
||||
switch mode {
|
||||
case let .receive(address):
|
||||
return walletInvoiceUrl(address: address)
|
||||
case let .invoice(address, amount, comment):
|
||||
return walletInvoiceUrl(address: address, amount: amount, comment: comment)
|
||||
}
|
||||
}
|
||||
|
||||
private final class WalletReceiveScreenNode: ViewControllerTracingNode {
|
||||
private let context: WalletContext
|
||||
private var presentationData: WalletPresentationData
|
||||
private let mode: WalletReceiveScreenMode
|
||||
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private let qrButtonNode: HighlightTrackingButtonNode
|
||||
private let qrImageNode: TransformImageNode
|
||||
private let qrIconNode: AnimatedStickerNode
|
||||
|
||||
private let urlTextNode: ImmediateTextNode
|
||||
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
private let secondaryButtonNode: HighlightableButtonNode
|
||||
|
||||
var openCreateInvoice: (() -> Void)?
|
||||
|
||||
init(context: WalletContext, presentationData: WalletPresentationData, mode: WalletReceiveScreenMode) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.mode = mode
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.textAlignment = .center
|
||||
self.textNode.maximumNumberOfLines = 3
|
||||
|
||||
self.qrImageNode = TransformImageNode()
|
||||
self.qrImageNode.clipsToBounds = true
|
||||
self.qrImageNode.cornerRadius = 14.0
|
||||
|
||||
self.qrIconNode = AnimatedStickerNode()
|
||||
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
|
||||
self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 240, height: 240, mode: .direct)
|
||||
self.qrIconNode.visibility = true
|
||||
}
|
||||
|
||||
self.qrButtonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.urlTextNode = ImmediateTextNode()
|
||||
self.urlTextNode.maximumNumberOfLines = 4
|
||||
self.urlTextNode.textAlignment = .justified
|
||||
self.urlTextNode.lineSpacing = 0.35
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: "", icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.setup.buttonFillColor, foregroundColor: self.presentationData.theme.setup.buttonForegroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
|
||||
self.secondaryButtonNode = HighlightableButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.qrImageNode)
|
||||
self.addSubnode(self.qrIconNode)
|
||||
self.addSubnode(self.qrButtonNode)
|
||||
self.addSubnode(self.urlTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.secondaryButtonNode)
|
||||
|
||||
self.qrImageNode.setSignal(qrCode(string: urlForMode(mode), color: .black, backgroundColor: .white, icon: .cutout), attemptSynchronously: true)
|
||||
|
||||
self.qrButtonNode.addTarget(self, action: #selector(self.qrPressed), forControlEvents: .touchUpInside)
|
||||
self.qrButtonNode.highligthedChanged = { [weak self] highlighted in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
strongSelf.qrImageNode.alpha = 0.4
|
||||
strongSelf.qrIconNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.qrImageNode.layer.animateAlpha(from: strongSelf.qrImageNode.alpha, to: 1.0, duration: 0.2)
|
||||
strongSelf.qrIconNode.layer.animateAlpha(from: strongSelf.qrIconNode.alpha, to: 1.0, duration: 0.2)
|
||||
strongSelf.qrImageNode.alpha = 1.0
|
||||
strongSelf.qrIconNode.alpha = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
let textFont = Font.regular(16.0)
|
||||
let addressFont = Font.monospace(17.0)
|
||||
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
|
||||
let secondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor
|
||||
let url = urlForMode(self.mode)
|
||||
switch self.mode {
|
||||
case let .receive(address):
|
||||
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_Receive_ShareUrlInfo, font: textFont, textColor: secondaryTextColor)
|
||||
self.urlTextNode.attributedText = NSAttributedString(string: formatAddress(url + " "), font: addressFont, textColor: textColor, paragraphAlignment: .justified)
|
||||
self.buttonNode.title = self.presentationData.strings.Wallet_Receive_ShareAddress
|
||||
self.secondaryButtonNode.setTitle(self.presentationData.strings.Wallet_Receive_CreateInvoice, with: Font.regular(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal)
|
||||
case let .invoice(address, amount, comment):
|
||||
self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_Receive_ShareUrlInfo, font: textFont, textColor: secondaryTextColor, paragraphAlignment: .center)
|
||||
|
||||
let sliced = String(url.enumerated().map { $0 > 0 && $0 % 32 == 0 ? ["\n", $1] : [$1]}.joined())
|
||||
self.urlTextNode.attributedText = NSAttributedString(string: sliced, font: addressFont, textColor: textColor, paragraphAlignment: .justified)
|
||||
self.buttonNode.title = self.presentationData.strings.Wallet_Receive_ShareInvoiceUrl
|
||||
}
|
||||
|
||||
self.buttonNode.pressed = {
|
||||
context.shareUrl(url)
|
||||
}
|
||||
self.secondaryButtonNode.addTarget(self, action: #selector(createInvoicePressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc private func qrPressed() {
|
||||
shareInvoiceQrCode(context: self.context, invoice: urlForMode(self.mode))
|
||||
}
|
||||
|
||||
@objc private func createInvoicePressed() {
|
||||
self.openCreateInvoice?()
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top += navigationHeight
|
||||
let inset: CGFloat = 22.0
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: insets.top + 24.0), size: textSize)
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
|
||||
let makeImageLayout = self.qrImageNode.asyncLayout()
|
||||
|
||||
let imageSide: CGFloat = 215.0
|
||||
var imageSize = CGSize(width: imageSide, height: imageSide)
|
||||
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
|
||||
|
||||
let _ = imageApply()
|
||||
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: textFrame.maxY + 20.0), size: imageSize)
|
||||
transition.updateFrame(node: self.qrImageNode, frame: imageFrame)
|
||||
transition.updateFrame(node: self.qrButtonNode, frame: imageFrame)
|
||||
|
||||
let iconSide = floor(imageSide * 0.24)
|
||||
let iconSize = CGSize(width: iconSide, height: iconSide)
|
||||
self.qrIconNode.updateLayout(size: iconSize)
|
||||
transition.updateBounds(node: self.qrIconNode, bounds: CGRect(origin: CGPoint(), size: iconSize))
|
||||
transition.updatePosition(node: self.qrIconNode, position: imageFrame.center.offsetBy(dx: 0.0, dy: -1.0))
|
||||
|
||||
let urlTextSize = self.urlTextNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
transition.updateFrame(node: self.urlTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - urlTextSize.width) / 2.0), y: imageFrame.maxY + 25.0), size: urlTextSize))
|
||||
|
||||
let buttonSideInset: CGFloat = 16.0
|
||||
let bottomInset = insets.bottom + 10.0
|
||||
let buttonWidth = layout.size.width - buttonSideInset * 2.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
|
||||
var buttonOffset: CGFloat = 0.0
|
||||
if let _ = self.secondaryButtonNode.attributedTitle(for: .normal) {
|
||||
buttonOffset = -60.0
|
||||
self.secondaryButtonNode.frame = CGRect(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight, width: buttonWidth, height: buttonHeight)
|
||||
}
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight + buttonOffset), size: CGSize(width: buttonWidth, height: buttonHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
|
||||
}
|
||||
}
|
||||
|
@ -55,20 +55,20 @@ private enum WalletSendScreenEntryTag: ItemListItemTag {
|
||||
}
|
||||
|
||||
private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
case amount(WalletTheme, String)
|
||||
case balance(WalletTheme, String, String, Bool)
|
||||
case addressHeader(WalletTheme, String)
|
||||
case address(WalletTheme, String, String)
|
||||
case addressInfo(WalletTheme, String)
|
||||
case amountHeader(WalletTheme, String, String?, Bool)
|
||||
case amount(WalletTheme, WalletStrings, String, String)
|
||||
case commentHeader(WalletTheme, String)
|
||||
case comment(WalletTheme, String, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .amount, .balance:
|
||||
return WalletSendScreenSection.amount.rawValue
|
||||
case .addressHeader, .address, .addressInfo:
|
||||
return WalletSendScreenSection.address.rawValue
|
||||
case .amountHeader, .amount:
|
||||
return WalletSendScreenSection.amount.rawValue
|
||||
case .commentHeader, .comment:
|
||||
return WalletSendScreenSection.comment.rawValue
|
||||
}
|
||||
@ -76,15 +76,15 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .addressHeader:
|
||||
return 0
|
||||
case .address:
|
||||
return 1
|
||||
case .addressInfo:
|
||||
return 2
|
||||
case .amountHeader:
|
||||
return 3
|
||||
case .amount:
|
||||
return 0
|
||||
case .balance:
|
||||
return 1
|
||||
case .addressHeader:
|
||||
return 2
|
||||
case .address:
|
||||
return 3
|
||||
case .addressInfo:
|
||||
return 4
|
||||
case .commentHeader:
|
||||
return 5
|
||||
@ -95,6 +95,18 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
|
||||
static func ==(lhs: WalletSendScreenEntry, rhs: WalletSendScreenEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .amount(lhsTheme, lhsAmount):
|
||||
if case let .amount(rhsTheme, rhsAmount) = rhs, lhsTheme === rhsTheme, lhsAmount == rhsAmount {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .balance(lhsTheme, lhsTitle, lhsBalance, lhsInsufficient):
|
||||
if case let .balance(rhsTheme, rhsTitle, rhsBalance, rhsInsufficient) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsBalance == rhsBalance, lhsInsufficient == rhsInsufficient {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addressHeader(lhsTheme, lhsText):
|
||||
if case let .addressHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -113,18 +125,6 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .amountHeader(lhsTheme, lhsText, lhsBalance, lhsInsufficient):
|
||||
if case let .amountHeader(rhsTheme, rhsText, rhsBalance, rhsInsufficient) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsBalance == rhsBalance, lhsInsufficient == rhsInsufficient {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .amount(lhsTheme, lhsStrings, lhsPlaceholder, lhsAmount):
|
||||
if case let .amount(rhsTheme, rhsStrings, rhsPlaceholder, rhsAmount) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPlaceholder == rhsPlaceholder, lhsAmount == rhsAmount {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .commentHeader(lhsTheme, lhsText):
|
||||
if case let .commentHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -147,6 +147,33 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! WalletSendScreenArguments
|
||||
switch self {
|
||||
case let .amount(theme, text):
|
||||
return WalletAmountItem(theme: theme, amount: text, sectionId: self.section, textUpdated: { text in
|
||||
let text = formatAmountText(text, decimalSeparator: arguments.context.presentationData.dateTimeFormat.decimalSeparator)
|
||||
arguments.updateText(WalletSendScreenEntryTag.amount, text)
|
||||
}, shouldUpdateText: { text in
|
||||
return isValidAmount(text)
|
||||
}, processPaste: { pastedText in
|
||||
if isValidAmount(pastedText) {
|
||||
let presentationData = arguments.context.presentationData
|
||||
return normalizedStringForGramsString(pastedText, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}, updatedFocus: { focus in
|
||||
if !focus {
|
||||
let presentationData = arguments.context.presentationData
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
if !state.amount.isEmpty {
|
||||
state.amount = normalizedStringForGramsString(state.amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
}, tag: WalletSendScreenEntryTag.amount)
|
||||
case let .balance(theme, title, balance, insufficient):
|
||||
return WalletBalanceItem(theme: theme, title: title, value: balance, insufficient: insufficient, sectionId: self.section)
|
||||
case let .addressHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .address(theme, placeholder, address):
|
||||
@ -190,35 +217,6 @@ private enum WalletSendScreenEntry: ItemListNodeEntry {
|
||||
}))
|
||||
case let .addressInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
|
||||
case let .amountHeader(theme, text, balance, insufficient):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, activityIndicator: balance == nil ? .right : .none, accessoryText: balance.flatMap { ItemListSectionHeaderAccessoryText(value: $0, color: insufficient ? .destructive : .generic, icon: balanceIcon) }, sectionId: self.section)
|
||||
case let .amount(theme, strings, placeholder, text):
|
||||
return ItemListSingleLineInputItem(theme: theme, strings: strings, title: NSAttributedString(string: ""), text: text, placeholder: placeholder, type: .decimal, returnKeyType: .next, clearType: .onFocus, tag: WalletSendScreenEntryTag.amount, sectionId: self.section, textUpdated: { text in
|
||||
let text = formatAmountText(text, decimalSeparator: arguments.context.presentationData.dateTimeFormat.decimalSeparator)
|
||||
arguments.updateText(WalletSendScreenEntryTag.amount, text)
|
||||
}, shouldUpdateText: { text in
|
||||
return isValidAmount(text)
|
||||
}, processPaste: { pastedText in
|
||||
if isValidAmount(pastedText) {
|
||||
let presentationData = arguments.context.presentationData
|
||||
return normalizedStringForGramsString(pastedText, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}, updatedFocus: { focus in
|
||||
if !focus {
|
||||
let presentationData = arguments.context.presentationData
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
if !state.amount.isEmpty {
|
||||
state.amount = normalizedStringForGramsString(state.amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
}, action: {
|
||||
arguments.selectNextInputItem(WalletSendScreenEntryTag.amount)
|
||||
})
|
||||
case let .commentHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .comment(theme, placeholder, value):
|
||||
@ -247,15 +245,15 @@ private func walletSendScreenEntries(presentationData: WalletPresentationData, b
|
||||
}
|
||||
var entries: [WalletSendScreenEntry] = []
|
||||
|
||||
let amount = amountValue(state.amount)
|
||||
let balance = max(0, balance ?? 0)
|
||||
entries.append(.amount(presentationData.theme, state.amount ?? ""))
|
||||
entries.append(.balance(presentationData.theme, presentationData.strings.Wallet_Send_Balance("").0, formatBalanceText(balance, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator), balance == 0 || (amount > 0 && balance < amount)))
|
||||
|
||||
entries.append(.addressHeader(presentationData.theme, presentationData.strings.Wallet_Send_AddressHeader))
|
||||
entries.append(.address(presentationData.theme, presentationData.strings.Wallet_Send_AddressText, state.address))
|
||||
entries.append(.addressInfo(presentationData.theme, presentationData.strings.Wallet_Send_AddressInfo))
|
||||
|
||||
let amount = amountValue(state.amount)
|
||||
let balance = max(0, balance ?? 0)
|
||||
entries.append(.amountHeader(presentationData.theme, presentationData.strings.Wallet_Receive_AmountHeader, presentationData.strings.Wallet_Send_Balance(formatBalanceText(balance, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)).0, balance == 0 || (amount > 0 && balance < amount)))
|
||||
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.strings.Wallet_Send_AmountText, state.amount ?? ""))
|
||||
|
||||
|
||||
entries.append(.commentHeader(presentationData.theme, presentationData.strings.Wallet_Receive_CommentHeader))
|
||||
entries.append(.comment(presentationData.theme, presentationData.strings.Wallet_Receive_CommentInfo, state.comment))
|
||||
return entries
|
||||
@ -450,10 +448,10 @@ public func walletSendScreen(context: WalletContext, randomId: Int64, walletInfo
|
||||
}
|
||||
|
||||
var focusItemTag: ItemListItemTag?
|
||||
if address == nil {
|
||||
focusItemTag = WalletSendScreenEntryTag.address
|
||||
} else if amount == nil {
|
||||
if amount == nil {
|
||||
focusItemTag = WalletSendScreenEntryTag.amount
|
||||
} else if address == nil {
|
||||
focusItemTag = WalletSendScreenEntryTag.address
|
||||
}
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), walletState, statePromise.get())
|
||||
@ -485,7 +483,7 @@ public func walletSendScreen(context: WalletContext, randomId: Int64, walletInfo
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
let controller = WalletSendScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil)
|
||||
let controller = WalletSendScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil, hasNavigationBarSeparator: false)
|
||||
controller.navigationPresentation = .modal
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
|
@ -385,6 +385,9 @@ public final class WalletSplashScreen: ViewController {
|
||||
if controller is WalletWordCheckScreen {
|
||||
return false
|
||||
}
|
||||
if controller is WalletTransactionInfoScreen {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -7,196 +7,48 @@ import SolidRoundedButtonNode
|
||||
import SwiftSignalKit
|
||||
import OverlayStatusController
|
||||
import WalletCore
|
||||
import AnimatedStickerNode
|
||||
|
||||
private func stringForFullDate(timestamp: Int32, strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat) -> String {
|
||||
var t: time_t = Int(timestamp)
|
||||
var timeinfo = tm()
|
||||
localtime_r(&t, &timeinfo);
|
||||
|
||||
let dayString = "\(timeinfo.tm_mday)"
|
||||
let yearString = "\(2000 + timeinfo.tm_year - 100)"
|
||||
let timeString = stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)
|
||||
|
||||
let monthFormat: (String, String, String) -> (String, [(Int, NSRange)])
|
||||
switch timeinfo.tm_mon + 1 {
|
||||
case 1:
|
||||
return strings.Wallet_Time_PreciseDate_m1("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m1
|
||||
case 2:
|
||||
return strings.Wallet_Time_PreciseDate_m2("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m2
|
||||
case 3:
|
||||
return strings.Wallet_Time_PreciseDate_m3("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m3
|
||||
case 4:
|
||||
return strings.Wallet_Time_PreciseDate_m4("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m4
|
||||
case 5:
|
||||
return strings.Wallet_Time_PreciseDate_m5("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m5
|
||||
case 6:
|
||||
return strings.Wallet_Time_PreciseDate_m6("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m6
|
||||
case 7:
|
||||
return strings.Wallet_Time_PreciseDate_m7("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m7
|
||||
case 8:
|
||||
return strings.Wallet_Time_PreciseDate_m8("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m8
|
||||
case 9:
|
||||
return strings.Wallet_Time_PreciseDate_m9("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m9
|
||||
case 10:
|
||||
return strings.Wallet_Time_PreciseDate_m10("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m10
|
||||
case 11:
|
||||
return strings.Wallet_Time_PreciseDate_m11("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m11
|
||||
case 12:
|
||||
return strings.Wallet_Time_PreciseDate_m12("\(timeinfo.tm_mday)", "\(2000 + timeinfo.tm_year - 100)", stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)).0
|
||||
monthFormat = strings.Wallet_Time_PreciseDate_m12
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private final class WalletTransactionInfoControllerArguments {
|
||||
let copyWalletAddress: () -> Void
|
||||
let sendGrams: () -> Void
|
||||
let displayContextMenu: (WalletTransactionInfoEntryTag, String) -> Void
|
||||
let openFeeInfo: () -> Void
|
||||
|
||||
init(copyWalletAddress: @escaping () -> Void, sendGrams: @escaping () -> Void, displayContextMenu: @escaping (WalletTransactionInfoEntryTag, String) -> Void, openFeeInfo: @escaping () -> Void) {
|
||||
self.copyWalletAddress = copyWalletAddress
|
||||
self.sendGrams = sendGrams
|
||||
self.displayContextMenu = displayContextMenu
|
||||
self.openFeeInfo = openFeeInfo
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletTransactionInfoSection: Int32 {
|
||||
case amount
|
||||
case info
|
||||
case storageFee
|
||||
case otherFee
|
||||
case comment
|
||||
}
|
||||
|
||||
private enum WalletTransactionInfoEntryTag: ItemListItemTag {
|
||||
case address
|
||||
case comment
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? WalletTransactionInfoEntryTag {
|
||||
return self == other
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletTransactionInfoEntry: ItemListNodeEntry {
|
||||
case amount(WalletTheme, WalletStrings, WalletPresentationDateTimeFormat, WalletInfoTransaction)
|
||||
case infoHeader(WalletTheme, String)
|
||||
case infoAddress(WalletTheme, String, String?)
|
||||
case infoCopyAddress(WalletTheme, String)
|
||||
case infoSendGrams(WalletTheme, String)
|
||||
case storageFeeHeader(WalletTheme, String)
|
||||
case storageFee(WalletTheme, String)
|
||||
case storageFeeInfo(WalletTheme, String)
|
||||
case otherFeeHeader(WalletTheme, String)
|
||||
case otherFee(WalletTheme, String)
|
||||
case otherFeeInfo(WalletTheme, String)
|
||||
case commentHeader(WalletTheme, String)
|
||||
case comment(WalletTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .amount:
|
||||
return WalletTransactionInfoSection.amount.rawValue
|
||||
case .infoHeader, .infoAddress, .infoCopyAddress, .infoSendGrams:
|
||||
return WalletTransactionInfoSection.info.rawValue
|
||||
case .storageFeeHeader, .storageFee, .storageFeeInfo:
|
||||
return WalletTransactionInfoSection.storageFee.rawValue
|
||||
case .otherFeeHeader, .otherFee, .otherFeeInfo:
|
||||
return WalletTransactionInfoSection.otherFee.rawValue
|
||||
case .commentHeader, .comment:
|
||||
return WalletTransactionInfoSection.comment.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .amount:
|
||||
return 0
|
||||
case .infoHeader:
|
||||
return 1
|
||||
case .infoAddress:
|
||||
return 2
|
||||
case .infoCopyAddress:
|
||||
return 3
|
||||
case .infoSendGrams:
|
||||
return 4
|
||||
case .commentHeader:
|
||||
return 5
|
||||
case .comment:
|
||||
return 6
|
||||
case .storageFeeHeader:
|
||||
return 7
|
||||
case .storageFee:
|
||||
return 8
|
||||
case .storageFeeInfo:
|
||||
return 9
|
||||
case .otherFeeHeader:
|
||||
return 10
|
||||
case .otherFee:
|
||||
return 11
|
||||
case .otherFeeInfo:
|
||||
return 12
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: WalletTransactionInfoEntry, rhs: WalletTransactionInfoEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(_ arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! WalletTransactionInfoControllerArguments
|
||||
switch self {
|
||||
case let .amount(theme, strings, dateTimeFormat, walletTransaction):
|
||||
return WalletTransactionHeaderItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, walletTransaction: walletTransaction, sectionId: self.section)
|
||||
case let .infoHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .infoAddress(theme, text, address):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, font: .monospace, sectionId: self.section, style: .blocks, longTapAction: address == nil ? nil : {
|
||||
if let address = address {
|
||||
arguments.displayContextMenu(WalletTransactionInfoEntryTag.address, address)
|
||||
}
|
||||
}, tag: WalletTransactionInfoEntryTag.address)
|
||||
case let .infoCopyAddress(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.copyWalletAddress()
|
||||
})
|
||||
case let .infoSendGrams(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.sendGrams()
|
||||
})
|
||||
case let .storageFeeHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .storageFee(theme, text):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil)
|
||||
case let .storageFeeInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section, linkAction: { action in
|
||||
switch action {
|
||||
case .tap:
|
||||
arguments.openFeeInfo()
|
||||
}
|
||||
}, style: .blocks)
|
||||
case let .otherFeeHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .otherFee(theme, text):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, sectionId: self.section, style: .blocks, longTapAction: nil, tag: nil)
|
||||
case let .otherFeeInfo(theme, text):
|
||||
return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section, linkAction: { action in
|
||||
switch action {
|
||||
case .tap:
|
||||
arguments.openFeeInfo()
|
||||
}
|
||||
}, style: .blocks)
|
||||
case let .commentHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .comment(theme, text):
|
||||
return ItemListMultilineTextItem(theme: theme, text: text, sectionId: self.section, style: .blocks, longTapAction: {
|
||||
arguments.displayContextMenu(WalletTransactionInfoEntryTag.comment, text)
|
||||
}, tag: WalletTransactionInfoEntryTag.comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct WalletTransactionInfoControllerState: Equatable {
|
||||
return monthFormat(dayString, yearString, timeString).0
|
||||
}
|
||||
|
||||
private enum WalletTransactionAddress {
|
||||
@ -266,359 +118,339 @@ private func extractDescription(_ walletTransaction: WalletInfoTransaction) -> S
|
||||
}
|
||||
}
|
||||
|
||||
private func walletTransactionInfoControllerEntries(presentationData: WalletPresentationData, walletTransaction: WalletInfoTransaction, state: WalletTransactionInfoControllerState, walletInfo: WalletInfo?) -> [WalletTransactionInfoEntry] {
|
||||
var entries: [WalletTransactionInfoEntry] = []
|
||||
private func messageBubbleImage(incoming: Bool, fillColor: UIColor, strokeColor: UIColor) -> UIImage {
|
||||
let diameter: CGFloat = 36.0
|
||||
let corner: CGFloat = 7.0
|
||||
|
||||
entries.append(.amount(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, walletTransaction))
|
||||
|
||||
let transferredValue: Int64
|
||||
switch walletTransaction {
|
||||
case let .completed(transaction):
|
||||
transferredValue = transaction.transferredValueWithoutFees
|
||||
case let .pending(transaction):
|
||||
transferredValue = -transaction.value
|
||||
}
|
||||
let address = extractAddress(walletTransaction)
|
||||
var singleAddress: String?
|
||||
let text = stringForAddress(strings: presentationData.strings, address: address)
|
||||
let description = extractDescription(walletTransaction)
|
||||
|
||||
if transferredValue <= 0 {
|
||||
entries.append(.infoHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_RecipientHeader))
|
||||
} else {
|
||||
entries.append(.infoHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SenderHeader))
|
||||
}
|
||||
var singleAddres: String?
|
||||
if case let .list(list) = address, list.count == 1 {
|
||||
singleAddres = list.first
|
||||
}
|
||||
entries.append(.infoAddress(presentationData.theme, text, singleAddres))
|
||||
if case .list = address, walletInfo != nil {
|
||||
entries.append(.infoCopyAddress(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_CopyAddress))
|
||||
entries.append(.infoSendGrams(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_SendGrams))
|
||||
}
|
||||
|
||||
if !description.isEmpty {
|
||||
entries.append(.commentHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_CommentHeader))
|
||||
entries.append(.comment(presentationData.theme, description))
|
||||
}
|
||||
|
||||
if case let .completed(transaction) = walletTransaction {
|
||||
if transaction.storageFee != 0 {
|
||||
entries.append(.storageFeeHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_StorageFeeHeader))
|
||||
entries.append(.storageFee(presentationData.theme, formatBalanceText(-transaction.storageFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.storageFeeInfo(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_StorageFeeInfo))
|
||||
}
|
||||
if transaction.otherFee != 0 {
|
||||
entries.append(.otherFeeHeader(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_OtherFeeHeader))
|
||||
entries.append(.otherFee(presentationData.theme, formatBalanceText(-transaction.otherFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)))
|
||||
entries.append(.otherFeeInfo(presentationData.theme, presentationData.strings.Wallet_TransactionInfo_OtherFeeInfo))
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
func walletTransactionInfoController(context: WalletContext, walletInfo: WalletInfo?, walletTransaction: WalletInfoTransaction, enableDebugActions: Bool) -> ViewController {
|
||||
let statePromise = ValuePromise(WalletTransactionInfoControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: WalletTransactionInfoControllerState())
|
||||
let updateState: ((WalletTransactionInfoControllerState) -> WalletTransactionInfoControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var displayContextMenuImpl: ((WalletTransactionInfoEntryTag, String) -> Void)?
|
||||
|
||||
let arguments = WalletTransactionInfoControllerArguments(copyWalletAddress: {
|
||||
let address = extractAddress(walletTransaction)
|
||||
if case let .list(addresses) = address, let address = addresses.first {
|
||||
UIPasteboard.general.string = address
|
||||
let presentationData = context.presentationData
|
||||
presentControllerImpl?(OverlayStatusController(theme: presentationData.theme, type: .genericSuccess(presentationData.strings.Wallet_TransactionInfo_AddressCopied, false)), nil)
|
||||
}
|
||||
}, sendGrams: {
|
||||
guard let walletInfo = walletInfo else {
|
||||
return
|
||||
}
|
||||
let address = extractAddress(walletTransaction)
|
||||
if case let .list(addresses) = address, let address = addresses.first {
|
||||
dismissImpl?()
|
||||
var randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
pushImpl?(walletSendScreen(context: context, randomId: randomId, walletInfo: walletInfo, address: address))
|
||||
}
|
||||
}, displayContextMenu: { tag, text in
|
||||
displayContextMenuImpl?(tag, text)
|
||||
}, openFeeInfo: {
|
||||
let presentationData = context.presentationData
|
||||
context.openUrl(presentationData.strings.Wallet_TransactionInfo_FeeInfoURL)
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_TransactionInfo_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletTransactionInfoControllerEntries(presentationData: presentationData, walletTransaction: walletTransaction, state: state, walletInfo: walletInfo), style: .blocks, animateChanges: false)
|
||||
return generateImage(CGSize(width: 42.0, height: diameter), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
}
|
||||
|
||||
let controller = ItemListController(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil)
|
||||
controller.navigationPresentation = .modal
|
||||
controller.enableInteractiveDismiss = true
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
pushImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
displayContextMenuImpl = { [weak controller] tag, value in
|
||||
if let strongController = controller {
|
||||
let presentationData = context.presentationData
|
||||
var resultItemNode: ListViewItemNode?
|
||||
let _ = strongController.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListMultilineTextItemNode {
|
||||
if let itemTag = itemNode.tag as? WalletTransactionInfoEntryTag {
|
||||
if itemTag == tag {
|
||||
resultItemNode = itemNode
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode {
|
||||
var actions: [ContextMenuAction] = []
|
||||
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Wallet_ContextMenuCopy, accessibilityLabel: presentationData.strings.Wallet_ContextMenuCopy), action: {
|
||||
UIPasteboard.general.string = value
|
||||
}))
|
||||
if enableDebugActions {
|
||||
if case .address = tag {
|
||||
actions.append(ContextMenuAction(content: .text(title: "View Transactions", accessibilityLabel: "View Transactions"), action: {
|
||||
pushImpl?(WalletInfoScreen(context: context, walletInfo: nil, address: value, enableDebugActions: enableDebugActions))
|
||||
}))
|
||||
}
|
||||
}
|
||||
let contextMenuController = ContextMenuController(actions: actions)
|
||||
strongController.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak resultItemNode] in
|
||||
if let strongController = controller, let resultItemNode = resultItemNode {
|
||||
return (resultItemNode, resultItemNode.contentBounds.insetBy(dx: 0.0, dy: -2.0), strongController.displayNode, strongController.view.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: incoming ? 1.0 : -1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0 + 0.5, y: -size.height / 2.0 + 0.5)
|
||||
|
||||
let lineWidth: CGFloat = 1.0
|
||||
context.setFillColor(fillColor.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(strokeColor.cgColor)
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M6,17.5 C6,7.83289181 13.8350169,0 23.5,0 C33.1671082,0 41,7.83501688 41,17.5 C41,27.1671082 33.1649831,35 23.5,35 C19.2941198,35 15.4354328,33.5169337 12.4179496,31.0453367 C9.05531719,34.9894816 -2.41102995e-08,35 0,35 C5.972003,31.5499861 6,26.8616169 6,26.8616169 L6,17.5 L6,17.5 ")
|
||||
context.strokePath()
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M6,17.5 C6,7.83289181 13.8350169,0 23.5,0 C33.1671082,0 41,7.83501688 41,17.5 C41,27.1671082 33.1649831,35 23.5,35 C19.2941198,35 15.4354328,33.5169337 12.4179496,31.0453367 C9.05531719,34.9894816 -2.41102995e-08,35 0,35 C5.972003,31.5499861 6,26.8616169 6,26.8616169 L6,17.5 L6,17.5 ")
|
||||
context.fillPath()
|
||||
})!.stretchableImage(withLeftCapWidth: incoming ? Int(corner + diameter / 2.0) : Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
|
||||
}
|
||||
|
||||
class WalletTransactionHeaderItem: ListViewItem, ItemListItem {
|
||||
let theme: WalletTheme
|
||||
let strings: WalletStrings
|
||||
let dateTimeFormat: WalletPresentationDateTimeFormat
|
||||
let walletTransaction: WalletInfoTransaction
|
||||
let sectionId: ItemListSectionId
|
||||
let isAlwaysPlain: Bool = true
|
||||
final class WalletTransactionInfoScreen: ViewController {
|
||||
private let context: WalletContext
|
||||
private let walletInfo: WalletInfo?
|
||||
private let walletTransaction: WalletInfoTransaction
|
||||
private var presentationData: WalletPresentationData
|
||||
|
||||
init(theme: WalletTheme, strings: WalletStrings, dateTimeFormat: WalletPresentationDateTimeFormat, walletTransaction: WalletInfoTransaction, sectionId: ItemListSectionId) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
private var previousScreenBrightness: CGFloat?
|
||||
private var displayLinkAnimator: DisplayLinkAnimator?
|
||||
private let idleTimerExtensionDisposable: Disposable
|
||||
|
||||
public init(context: WalletContext, walletInfo: WalletInfo?, walletTransaction: WalletInfoTransaction, enableDebugActions: Bool) {
|
||||
self.context = context
|
||||
self.walletInfo = walletInfo
|
||||
self.walletTransaction = walletTransaction
|
||||
self.sectionId = sectionId
|
||||
|
||||
self.presentationData = context.presentationData
|
||||
|
||||
let defaultTheme = self.presentationData.theme.navigationBar
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
|
||||
|
||||
self.idleTimerExtensionDisposable = context.idleTimerExtension()
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Wallet_Navigation_Back, close: self.presentationData.strings.Wallet_Navigation_Close)))
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.navigationBar?.intrinsicCanTransitionInline = false
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Back, style: .plain, target: nil, action: nil)
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
}
|
||||
|
||||
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 = WalletTransactionHeaderItemNode()
|
||||
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() })
|
||||
})
|
||||
}
|
||||
}
|
||||
deinit {
|
||||
self.idleTimerExtensionDisposable.dispose()
|
||||
}
|
||||
|
||||
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 {
|
||||
guard let nodeValue = node() as? WalletTransactionHeaderItemNode else {
|
||||
assertionFailure()
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = WalletTransactionInfoScreenNode(context: self.context, presentationData: self.presentationData, walletTransaction: self.walletTransaction)
|
||||
(self.displayNode as! WalletTransactionInfoScreenNode).send = { [weak self] address in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
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 randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
if let walletInfo = strongSelf.walletInfo {
|
||||
strongSelf.push(walletSendScreen(context: strongSelf.context, randomId: randomId, walletInfo: walletInfo, address: address))
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.regular(14.0)
|
||||
private let titleBoldFont = Font.semibold(14.0)
|
||||
|
||||
private class WalletTransactionHeaderItemNode: ListViewItemNode {
|
||||
private let titleSignNode: TextNode
|
||||
private let titleNode: TextNode
|
||||
private let subtitleNode: TextNode
|
||||
private let iconNode: ASImageNode
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: WalletTransactionHeaderItem?
|
||||
|
||||
init() {
|
||||
self.titleSignNode = TextNode()
|
||||
self.titleSignNode.isUserInteractionEnabled = false
|
||||
self.titleSignNode.contentMode = .left
|
||||
self.titleSignNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.subtitleNode = TextNode()
|
||||
self.subtitleNode.isUserInteractionEnabled = false
|
||||
self.subtitleNode.contentMode = .left
|
||||
self.subtitleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.image = UIImage(bundleImageName: "Wallet/BalanceGem")?.precomposed()
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
self.activateArea.accessibilityTraits = .staticText
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleSignNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: WalletTransactionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleSignLayout = TextNode.asyncLayout(self.titleSignNode)
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||
let iconSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 10.0)
|
||||
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||
let verticalInset: CGFloat = 24.0
|
||||
|
||||
let signString: String
|
||||
let balanceString: String
|
||||
let titleColor: UIColor
|
||||
let transferredValue: Int64
|
||||
switch item.walletTransaction {
|
||||
case let .completed(transaction):
|
||||
transferredValue = transaction.transferredValueWithoutFees
|
||||
case let .pending(transaction):
|
||||
transferredValue = -transaction.value
|
||||
(self.displayNode as! WalletTransactionInfoScreenNode).displayFeesTooltip = { [weak self] node, rect in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if transferredValue <= 0 {
|
||||
signString = ""
|
||||
balanceString = "\(formatBalanceText(-transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
||||
titleColor = item.theme.info.outgoingFundsTitleColor
|
||||
} else {
|
||||
signString = ""
|
||||
balanceString = "\(formatBalanceText(transferredValue, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
|
||||
titleColor = item.theme.info.incomingFundsTitleColor
|
||||
}
|
||||
|
||||
let title = NSMutableAttributedString()
|
||||
if let range = balanceString.range(of: item.dateTimeFormat.decimalSeparator) {
|
||||
let integralPart = String(balanceString[..<range.lowerBound])
|
||||
let fractionalPart = String(balanceString[range.lowerBound...])
|
||||
title.append(NSAttributedString(string: integralPart, font: Font.bold(48.0), textColor: titleColor))
|
||||
title.append(NSAttributedString(string: fractionalPart, font: Font.bold(24.0), textColor: titleColor))
|
||||
} else {
|
||||
title.append(NSAttributedString(string: balanceString, font: Font.bold(48.0), textColor: titleColor))
|
||||
}
|
||||
let titleSign = NSAttributedString(string: signString, font: Font.bold(48.0), textColor: titleColor)
|
||||
|
||||
let timestamp: Int64
|
||||
switch item.walletTransaction {
|
||||
case let .completed(transaction):
|
||||
timestamp = transaction.timestamp
|
||||
case let .pending(transaction):
|
||||
timestamp = transaction.timestamp
|
||||
}
|
||||
let subtitle: String = stringForFullDate(timestamp: Int32(clamping: timestamp), strings: item.strings, dateTimeFormat: item.dateTimeFormat)
|
||||
|
||||
let (titleSignLayout, titleSignApply) = makeTitleSignLayout(TextNodeLayoutArguments(attributedString: titleSign, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: Font.regular(13.0), textColor: item.theme.list.freeTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize: CGSize
|
||||
|
||||
contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset + verticalInset)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
|
||||
let titleScale: CGFloat = min(1.0, (params.width - 40.0 - iconSize.width) / titleLayout.size.width)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
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)
|
||||
strongSelf.present(controller, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: {
|
||||
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 = attributedText.string
|
||||
|
||||
let _ = titleSignApply()
|
||||
let _ = titleApply()
|
||||
let _ = subtitleApply()
|
||||
|
||||
let iconSpacing: CGFloat = 4.0
|
||||
let contentWidth = titleSignLayout.size.width + iconSpacing + titleLayout.size.width + iconSpacing + iconSize.width * 3.0 / 2.0
|
||||
let contentOrigin = floor((params.width - contentWidth) / 2.0)
|
||||
let titleSignFrame = CGRect(origin: CGPoint(x: contentOrigin, y: verticalInset), size: titleSignLayout.size)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: contentOrigin + titleSignFrame.width * titleScale + iconSpacing, y: titleSignFrame.minY + floor((titleLayout.size.height - iconSize.height) / 2.0) - 2.0), size: iconSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + iconSpacing, y: verticalInset), size: titleLayout.size)
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: floor((params.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY - 5.0), size: subtitleLayout.size)
|
||||
strongSelf.titleSignNode.position = titleSignFrame.center
|
||||
strongSelf.titleSignNode.bounds = CGRect(origin: CGPoint(), size: titleSignFrame.size)
|
||||
strongSelf.titleSignNode.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
|
||||
strongSelf.titleNode.position = titleFrame.center
|
||||
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
strongSelf.titleNode.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
|
||||
strongSelf.subtitleNode.frame = subtitleFrame
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
return (node.view, rect.insetBy(dx: 0.0, dy: -4.0))
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
private let measureTextNode = TextNode()
|
||||
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
let text = NSAttributedString(string: extractDescription(self.walletTransaction), font: Font.regular(17.0), textColor: .black)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.measureTextNode)
|
||||
let (textLayout, _) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: layout.size.width - 36.0 * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
var textHeight = textLayout.size.height
|
||||
if textHeight > 0.0 {
|
||||
textHeight += 24.0
|
||||
}
|
||||
let insets = layout.insets(options: [])
|
||||
return CGSize(width: layout.size.width, height: 428.0 + insets.bottom + textHeight)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! WalletTransactionInfoScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
@objc private func donePressed() {
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private let integralFont = Font.medium(48.0)
|
||||
private let fractionalFont = Font.medium(24.0)
|
||||
|
||||
private final class WalletTransactionInfoScreenNode: ViewControllerTracingNode {
|
||||
private let context: WalletContext
|
||||
private var presentationData: WalletPresentationData
|
||||
private let walletTransaction: WalletInfoTransaction
|
||||
private let incoming: Bool
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let timeNode: ImmediateTextNode
|
||||
|
||||
private let amountNode: ImmediateTextNode
|
||||
private let iconNode: AnimatedStickerNode
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
private let feesNode: ImmediateTextNode
|
||||
private let feesButtonNode: ASButtonNode
|
||||
|
||||
private let commentBackgroundNode: ASImageNode
|
||||
private let commentTextNode: ImmediateTextNode
|
||||
|
||||
private let addressTextNode: ImmediateTextNode
|
||||
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
|
||||
var send: ((String) -> Void)?
|
||||
var displayFeesTooltip: ((ASDisplayNode, CGRect) -> Void)?
|
||||
|
||||
init(context: WalletContext, presentationData: WalletPresentationData, walletTransaction: WalletInfoTransaction) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.walletTransaction = walletTransaction
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.textAlignment = .center
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
|
||||
self.timeNode = ImmediateTextNode()
|
||||
self.timeNode.textAlignment = .center
|
||||
self.timeNode.maximumNumberOfLines = 1
|
||||
|
||||
self.amountNode = ImmediateTextNode()
|
||||
self.amountNode.textAlignment = .center
|
||||
self.amountNode.maximumNumberOfLines = 1
|
||||
|
||||
self.iconNode = AnimatedStickerNode()
|
||||
if let path = getAppBundle().path(forResource: "WalletIntroStatic", ofType: "tgs") {
|
||||
self.iconNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 120, height: 120, mode: .direct)
|
||||
self.iconNode.visibility = true
|
||||
}
|
||||
|
||||
self.feesNode = ImmediateTextNode()
|
||||
self.feesNode.textAlignment = .center
|
||||
self.feesNode.maximumNumberOfLines = 2
|
||||
self.feesNode.lineSpacing = 0.35
|
||||
|
||||
self.feesButtonNode = ASButtonNode()
|
||||
|
||||
self.commentBackgroundNode = ASImageNode()
|
||||
self.commentBackgroundNode.contentMode = .scaleToFill
|
||||
|
||||
self.commentTextNode = ImmediateTextNode()
|
||||
self.commentTextNode.textAlignment = .natural
|
||||
self.commentTextNode.maximumNumberOfLines = 0
|
||||
|
||||
self.addressTextNode = ImmediateTextNode()
|
||||
self.addressTextNode.maximumNumberOfLines = 4
|
||||
self.addressTextNode.textAlignment = .justified
|
||||
self.addressTextNode.lineSpacing = 0.35
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: "", icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.setup.buttonFillColor, foregroundColor: self.presentationData.theme.setup.buttonForegroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
|
||||
let timestamp: Int64
|
||||
let transferredValue: Int64
|
||||
switch walletTransaction {
|
||||
case let .completed(transaction):
|
||||
timestamp = transaction.timestamp
|
||||
transferredValue = transaction.transferredValueWithoutFees
|
||||
case let .pending(transaction):
|
||||
timestamp = transaction.timestamp
|
||||
transferredValue = -transaction.value
|
||||
}
|
||||
self.incoming = transferredValue > 0
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.timeNode)
|
||||
self.addSubnode(self.amountNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.feesNode)
|
||||
self.addSubnode(self.feesButtonNode)
|
||||
self.addSubnode(self.commentBackgroundNode)
|
||||
self.addSubnode(self.commentTextNode)
|
||||
self.addSubnode(self.addressTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
let titleFont = Font.semibold(17.0)
|
||||
let subtitleFont = Font.regular(13.0)
|
||||
let addressFont = Font.monospace(17.0)
|
||||
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
|
||||
let seccondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Wallet_TransactionInfo_Title, font: titleFont, textColor: textColor)
|
||||
|
||||
|
||||
self.timeNode.attributedText = NSAttributedString(string: stringForFullDate(timestamp: Int32(clamping: timestamp), strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat), font: subtitleFont, textColor: seccondaryTextColor)
|
||||
|
||||
let amountString: String
|
||||
let amountColor: UIColor
|
||||
if transferredValue <= 0 {
|
||||
amountString = "\(formatBalanceText(-transferredValue, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator))"
|
||||
amountColor = self.presentationData.theme.info.outgoingFundsTitleColor
|
||||
} else {
|
||||
amountString = "\(formatBalanceText(transferredValue, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator))"
|
||||
amountColor = self.presentationData.theme.info.incomingFundsTitleColor
|
||||
}
|
||||
self.amountNode.attributedText = amountAttributedString(amountString, integralFont: integralFont, fractionalFont: fractionalFont, color: amountColor)
|
||||
|
||||
var feesString: String = ""
|
||||
if case let .completed(transaction) = walletTransaction {
|
||||
if transaction.storageFee != 0 {
|
||||
feesString.append(formatBalanceText(transaction.storageFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + " storage fee")
|
||||
}
|
||||
if transaction.otherFee != 0 {
|
||||
if !feesString.isEmpty {
|
||||
feesString.append("\n")
|
||||
}
|
||||
feesString.append(formatBalanceText(transaction.otherFee, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + " transaction fee")
|
||||
}
|
||||
|
||||
if !feesString.isEmpty {
|
||||
feesString.append("(?)")
|
||||
}
|
||||
}
|
||||
self.feesNode.attributedText = NSAttributedString(string: feesString, font: subtitleFont, textColor: seccondaryTextColor)
|
||||
|
||||
self.feesButtonNode.addTarget(self, action: #selector(feesPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.commentBackgroundNode.image = messageBubbleImage(incoming: transferredValue > 0, fillColor: UIColor(rgb: 0xf1f1f5), strokeColor: UIColor(rgb: 0xf1f1f5))
|
||||
self.commentTextNode.attributedText = NSAttributedString(string: extractDescription(walletTransaction), font: Font.regular(17.0), textColor: .black)
|
||||
|
||||
let address = extractAddress(walletTransaction)
|
||||
var singleAddress: String?
|
||||
if case let .list(list) = address, list.count == 1 {
|
||||
singleAddress = list.first
|
||||
}
|
||||
|
||||
if let address = singleAddress {
|
||||
self.addressTextNode.attributedText = NSAttributedString(string: formatAddress(address), font: addressFont, textColor: textColor, paragraphAlignment: .justified)
|
||||
self.buttonNode.title = "Send Grams to This Address"
|
||||
|
||||
self.buttonNode.pressed = { [weak self] in
|
||||
self?.send?(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
@objc private func feesPressed() {
|
||||
self.displayFeesTooltip?(self.feesNode, self.feesNode.bounds)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top += navigationHeight
|
||||
let inset: CGFloat = 22.0
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: 10.0), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
|
||||
let subtitleSize = self.timeNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize)
|
||||
transition.updateFrame(node: self.timeNode, frame: subtitleFrame)
|
||||
|
||||
let amountSize = self.amountNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let amountFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - amountSize.width) / 2.0) + 18.0, y: 90.0), size: amountSize)
|
||||
transition.updateFrame(node: self.amountNode, frame: amountFrame)
|
||||
|
||||
let iconSize = CGSize(width: 50.0, height: 50.0)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: amountFrame.minX - iconSize.width, y: amountFrame.minY), size: iconSize)
|
||||
self.iconNode.updateLayout(size: iconFrame.size)
|
||||
self.iconNode.frame = iconFrame
|
||||
|
||||
let feesSize = self.feesNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let feesFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - feesSize.width) / 2.0), y: amountFrame.maxY + 8.0), size: feesSize)
|
||||
transition.updateFrame(node: self.feesNode, frame: feesFrame)
|
||||
transition.updateFrame(node: self.feesButtonNode, frame: feesFrame)
|
||||
|
||||
let commentSize = self.commentTextNode.updateLayout(CGSize(width: layout.size.width - 36.0 * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let commentFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - commentSize.width) / 2.0), y: amountFrame.maxY + 84.0), size: CGSize(width: commentSize.width, height: commentSize.height))
|
||||
transition.updateFrame(node: self.commentTextNode, frame: commentFrame)
|
||||
|
||||
var commentBackgroundFrame = commentSize.width > 0.0 ? commentFrame.insetBy(dx: -11.0, dy: -7.0) : CGRect()
|
||||
commentBackgroundFrame.size.width += 7.0
|
||||
if self.incoming {
|
||||
commentBackgroundFrame.origin.x -= 7.0
|
||||
}
|
||||
transition.updateFrame(node: self.commentBackgroundNode, frame: commentBackgroundFrame)
|
||||
|
||||
let buttonSideInset: CGFloat = 16.0
|
||||
let bottomInset = insets.bottom + 10.0
|
||||
let buttonWidth = layout.size.width - buttonSideInset * 2.0
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
|
||||
|
||||
let addressSize = self.addressTextNode.updateLayout(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
transition.updateFrame(node: self.addressTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - addressSize.width) / 2.0), y: buttonFrame.minY - addressSize.height - 44.0), size: addressSize))
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import UrlEscaping
|
||||
|
||||
let walletAddressLength: Int = 48
|
||||
@ -186,3 +187,17 @@ func walletInvoiceUrl(address: String, amount: String? = nil, comment: String? =
|
||||
}
|
||||
return "ton://transfer/\(address)\(arguments)"
|
||||
}
|
||||
|
||||
private let amountDelimeterCharacters = CharacterSet(charactersIn: "0123456789").inverted
|
||||
func amountAttributedString(_ string: String, integralFont: UIFont, fractionalFont: UIFont, color: UIColor) -> NSAttributedString {
|
||||
let result = NSMutableAttributedString()
|
||||
if let range = string.rangeOfCharacter(from: amountDelimeterCharacters) {
|
||||
let integralPart = String(string[..<range.lowerBound])
|
||||
let fractionalPart = String(string[range.lowerBound...])
|
||||
result.append(NSAttributedString(string: integralPart, font: integralFont, textColor: color))
|
||||
result.append(NSAttributedString(string: fractionalPart, font: fractionalFont, textColor: color))
|
||||
} else {
|
||||
result.append(NSAttributedString(string: string, font: integralFont, textColor: color))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user