Wallet: limit amount input to Int64.max

This commit is contained in:
Ilya Laktyushin 2019-09-27 11:32:22 +03:00
parent a8628602dd
commit d0d98ba15a
8 changed files with 101 additions and 17 deletions

View File

@ -106,7 +106,7 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
let _ = try? drawSvgPath(c, path: "M24.1237113,50.5927835 L40.8762887,50.5927835 L40.8762887,60.9793814 L32.5,64.0928525 L24.1237113,60.9793814 Z")
case let .custom(image):
if let image = image {
let fittedSize = image.size.fitted(clipRect.size)
let fittedSize = image.size.aspectFitted(clipRect.size)
let fittedRect = CGRect(origin: CGPoint(x: fittedRect.midX - fittedSize.width / 2.0, y: fittedRect.midY - fittedSize.height / 2.0), size: fittedSize)
c.translateBy(x: fittedRect.midX, y: fittedRect.midY)
c.scaleBy(x: 1.0, y: -1.0)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -13,14 +13,16 @@ class WalletInfoEmptyItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let address: String
let displayAddressContextMenu: (ASDisplayNode, CGRect) -> Void
let selectable: Bool = false
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, address: String) {
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, address: String, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) {
self.account = account
self.theme = theme
self.strings = strings
self.address = address
self.displayAddressContextMenu = displayAddressContextMenu
}
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) {
@ -62,6 +64,8 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
private let textNode: TextNode
private let addressNode: TextNode
private var item: WalletInfoEmptyItem?
init(account: Account) {
self.offsetContainer = ASDisplayNode()
@ -88,6 +92,32 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
self.addSubnode(self.offsetContainer)
}
override func didLoad() {
super.didLoad()
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { [weak self] point in
return .waitForSingleTap
}
self.addressNode.view.addGestureRecognizer(recognizer)
}
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .longTap:
self.item?.displayAddressContextMenu(self, self.addressNode.frame)
default:
break
}
}
default:
break
}
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let layout = self.asyncLayout()
let (_, apply) = layout(item as! WalletInfoEmptyItem, params)
@ -139,6 +169,7 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
guard let strongSelf = self else {
return
}
strongSelf.item = item
strongSelf.offsetContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)

View File

@ -80,11 +80,11 @@ public final class WalletInfoScreen: ViewController {
return
}
strongSelf.push(walletTransactionInfoController(context: strongSelf.context, tonContext: strongSelf.tonContext, walletInfo: strongSelf.walletInfo, walletTransaction: transaction))
}, present: { [weak self] c in
}, present: { [weak self] c, a in
guard let strongSelf = self else {
return
}
strongSelf.present(c, in: .window(.root))
strongSelf.present(c, in: .window(.root), with: a)
})
self.displayNodeDidLoad()
@ -403,10 +403,12 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, action: @escaping (WalletTransaction) -> Void) -> ListViewItem {
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, action: @escaping (WalletTransaction) -> Void, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) -> ListViewItem {
switch self {
case let .empty(address):
return WalletInfoEmptyItem(account: account, theme: theme, strings: strings, address: address)
return WalletInfoEmptyItem(account: account, theme: theme, strings: strings, address: address, displayAddressContextMenu: { node, frame in
displayAddressContextMenu(node, frame)
})
case let .transaction(_, transaction):
return WalletInfoTransactionItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, walletTransaction: transaction, action: {
action(transaction)
@ -415,12 +417,12 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], account: Account, presentationData: PresentationData, action: @escaping (WalletTransaction) -> Void) -> WalletInfoListTransaction {
private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], account: Account, presentationData: PresentationData, action: @escaping (WalletTransaction) -> Void, displayAddressContextMenu: @escaping (ASDisplayNode, CGRect) -> Void) -> WalletInfoListTransaction {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action, displayAddressContextMenu: displayAddressContextMenu), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, action: action, displayAddressContextMenu: displayAddressContextMenu), directionHint: nil) }
return WalletInfoListTransaction(deletions: deletions, insertions: insertions, updates: updates)
}
@ -433,7 +435,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private let address: String
private let openTransaction: (WalletTransaction) -> Void
private let present: (ViewController) -> Void
private let present: (ViewController, Any?) -> Void
private let hapticFeedback = HapticFeedback()
@ -463,7 +465,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
private var updateTimestampTimer: SwiftSignalKit.Timer?
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void, present: @escaping (ViewController) -> Void) {
init(account: Account, tonContext: TonContext, presentationData: PresentationData, walletInfo: WalletInfo, address: String, sendAction: @escaping () -> Void, receiveAction: @escaping () -> Void, openTransaction: @escaping (WalletTransaction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.account = account
self.tonContext = tonContext
self.presentationData = presentationData
@ -740,7 +742,7 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: "An Error Occurred", text: "The wallet state can not be retrieved at this time. Please try again later.", actions: [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
})
], actionLayout: .vertical))
], actionLayout: .vertical), nil)
}))
}
@ -821,6 +823,21 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
return
}
strongSelf.openTransaction(transaction)
}, displayAddressContextMenu: { [weak self] node, frame in
guard let strongSelf = self else {
return
}
let address = strongSelf.address
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: strongSelf.presentationData.strings.Conversation_ContextMenuCopy), action: {
UIPasteboard.general.string = address
})])
strongSelf.present(contextMenuController, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
if let strongSelf = self {
return (node, frame.insetBy(dx: 0.0, dy: -2.0), strongSelf, strongSelf.view.bounds)
} else {
return nil
}
}))
})
self.currentEntries = updatedEntries

View File

@ -100,14 +100,13 @@ public final class WalletQrScanScreen: ViewController {
self.displayNodeDidLoad()
self.codeDisposable = (((self.displayNode as! WalletQrScanScreenNode).focusedCode.get()
self.codeDisposable = ((self.displayNode as! WalletQrScanScreenNode).focusedCode.get()
|> map { code -> String? in
return code?.message
}
|> distinctUntilChanged
|> delay(2.5, queue: Queue.mainQueue()))
|> mapToSignal { code -> Signal<String?, NoError> in
return .single(code)
return .single(code) |> delay(0.5, queue: Queue.mainQueue())
}).start(next: { [weak self] code in
guard let strongSelf = self, let code = code else {
return
@ -270,13 +269,16 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
let dimAlpha: CGFloat
let dimRect: CGRect
let controlsAlpha: CGFloat
if let focusedRect = self.focusedRect {
dimAlpha = 1.0
controlsAlpha = 0.0
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height)
dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side)
} else {
dimAlpha = 0.625
controlsAlpha = 1.0
dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
}
@ -296,6 +298,10 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
transition.updateFrame(node: self.galleryButtonNode, frame: CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0) - buttonSize.width - 28.0, y: dimHeight + frameSide + 50.0), size: buttonSize))
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0) + 28.0, y: dimHeight + frameSide + 50.0), size: buttonSize))
transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.galleryButtonNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: dimHeight - titleSize.height - titleSpacing), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)

View File

@ -63,11 +63,41 @@ func isValidAmount(_ amount: String) -> Bool {
return false
}
let string = amount.replacingOccurrences(of: ",", with: ".")
if let range = string.range(of: ".") {
let integralPart = String(string[..<range.lowerBound])
let fractionalPart = String(string[range.upperBound...])
let string = integralPart + fractionalPart + String(repeating: "0", count: max(0, 9 - fractionalPart.count))
if let _ = Int64(string) {
} else {
return false
}
} else if !string.isEmpty {
if let integral = Int64(string), integral <= maxIntegral {
} else {
return false
}
}
return true
}
private let maxIntegral: Int64 = Int64.max / 1000000000
func amountValue(_ string: String) -> Int64 {
return Int64((Double(string.replacingOccurrences(of: ",", with: ".")) ?? 0.0) * 1000000000.0)
let string = string.replacingOccurrences(of: ",", with: ".")
if let range = string.range(of: ".") {
let integralPart = String(string[..<range.lowerBound])
let fractionalPart = String(string[range.upperBound...])
let string = integralPart + fractionalPart + String(repeating: "0", count: max(0, 9 - fractionalPart.count))
return Int64(string) ?? 0
} else if let integral = Int64(string) {
if integral > maxIntegral {
return 0
}
return integral * 1000000000
}
return 0
}
func normalizedStringForGramsString(_ string: String, decimalSeparator: String = ".") -> String {

View File

@ -2706,7 +2706,7 @@ private final class WalletWordCheckScreenNode: ViewControllerTracingNode, UIScro
guard let strongSelf = self else {
return
}
if node === strongSelf.inputNodes.last {
if node.isLast {
if done {
action()
} else {