This commit is contained in:
Peter 2019-09-27 18:34:26 +04:00
commit d034233388
18 changed files with 1361 additions and 1229 deletions

View file

@ -4820,6 +4820,7 @@ Any member of this group will be able to see messages in the channel.";
"Wallet.Intro.Text" = "Gram wallet allows you to make fast and secure blockchain-based payments without intermediaries.";
"Wallet.Intro.CreateWallet" = "Create My Wallet";
"Wallet.Intro.Terms" = "By creating the wallet you accept\n[Terms of Conditions]().";
"Wallet.Intro.TermsUrl" = "https://telegram.org/tos/wallet";
"Wallet.Created.Title" = "Congratulations";
"Wallet.Created.Text" = "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down secret words and\nset up a secure passcode.";
"Wallet.Created.Proceed" = "Proceed";

View file

@ -441,6 +441,7 @@ public protocol SharedAccountContext: class {
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
func openWallet(context: AccountContext, walletContext: OpenWalletContext, present: @escaping (ViewController) -> Void)
func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void)
func navigateToCurrentCall()
var hasOngoingCall: ValuePromise<Bool> { get }

View file

@ -822,6 +822,8 @@ static id<LegacyComponentsContext> _defaultContext = nil;
- (CGFloat)_currentStatusBarHeight
{
if (_context.isStatusBarHidden)
return 0.0;
if (_context.safeAreaInset.top > 20.0f)
return _context.safeAreaInset.top;
@ -1127,7 +1129,7 @@ static id<LegacyComponentsContext> _defaultContext = nil;
UIEdgeInsets safeAreaInset = [TGViewController safeAreaInsetForOrientation:orientation hasOnScreenNavigation:hasOnScreenNavigation];
CGFloat navigationBarHeight = ([self navigationBarShouldBeHidden] || [self shouldIgnoreNavigationBar]) ? 0 : [self navigationBarHeightForInterfaceOrientation:orientation];
statusBarHeight = safeAreaInset.top > FLT_EPSILON ? safeAreaInset.top : statusBarHeight;
//statusBarHeight = safeAreaInset.top > FLT_EPSILON ? safeAreaInset.top : statusBarHeight;
UIEdgeInsets edgeInset = UIEdgeInsetsMake(([self shouldIgnoreStatusBarInOrientation:orientation] ? 0.0f : statusBarHeight) + navigationBarHeight, 0, 0, 0);

View file

@ -122,7 +122,7 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
public func isStatusBarHidden() -> Bool {
if let controller = self.controller {
return controller.statusBar.isHidden
return controller.statusBar.isHidden || controller.navigationPresentation == .modal
} else {
return true
}
@ -282,6 +282,9 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
if validLayout.intrinsicInsets.bottom.isEqual(to: 21.0) {
safeInsets.bottom = 21.0
}
if controller.navigationPresentation == .modal {
safeInsets.top = 0.0
}
return safeInsets
}
return UIEdgeInsets()

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

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -5345,7 +5345,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
strongSelf.chatDisplayNode.dismissInput()
legacyController.navigationPresentation = .modal
(strongSelf.navigationController as? NavigationController)?.pushViewController(legacyController)
}
})

View file

@ -15,6 +15,8 @@ import PeerInfoUI
import SettingsUI
import UrlHandling
import WalletUI
import LegacyMediaPickerUI
import LocalMediaResources
private enum CallStatusText: Equatable {
case none
@ -1063,6 +1065,38 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
})
}
public func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = legacyWallpaperPicker(context: context, presentationData: presentationData).start(next: { generator in
let legacyController = LegacyController(presentation: .navigation, theme: presentationData.theme)
legacyController.navigationPresentation = .modal
legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
let controller = generator(legacyController.context)
legacyController.bind(controller: controller)
legacyController.deferScreenEdgeGestures = [.top]
controller.selectionBlock = { [weak legacyController, weak controller] asset, _ in
if let asset = asset {
let _ = (fetchPhotoLibraryImage(localIdentifier: asset.backingAsset.localIdentifier, thumbnail: false)
|> deliverOnMainQueue).start(next: { imageAndFlag in
if let (image, _) = imageAndFlag {
completion(image)
}
})
if let legacyController = legacyController {
legacyController.dismiss()
}
}
}
controller.dismissalBlock = { [weak legacyController] in
if let legacyController = legacyController {
legacyController.dismiss()
}
}
present(legacyController)
})
}
}
private let defaultChatControllerInteraction = ChatControllerInteraction.default

View file

@ -32,5 +32,6 @@ static_library(
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/CoreImage.framework",
],
)

View file

@ -13,16 +13,16 @@ class WalletInfoEmptyItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let address: String
let presentAddressContextMenu: (ASDisplayNode) -> Void
let displayAddressContextMenu: (ASDisplayNode, CGRect) -> Void
let selectable: Bool = false
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, address: String, presentAddressContextMenu: @escaping (ASDisplayNode) -> Void) {
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.presentAddressContextMenu = presentAddressContextMenu
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) {
@ -95,15 +95,26 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
override func didLoad() {
super.didLoad()
self.addressNode.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:))))
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
recognizer.tapActionAtPoint = { [weak self] point in
return .waitForSingleTap
}
self.addressNode.view.addGestureRecognizer(recognizer)
}
@objc private func longPressGesture(_ recognizer: UILongPressGestureRecognizer) {
if case .began = recognizer.state {
guard let item = self.item else {
return
@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
}
}
item.presentAddressContextMenu(self.addressNode)
default:
break
}
}
@ -158,6 +169,7 @@ final class WalletInfoEmptyItemNode: ListViewItemNode {
guard let strongSelf = self else {
return
}
strongSelf.item = item
strongSelf.item = item

View file

@ -410,10 +410,12 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, action: @escaping (WalletTransaction) -> Void, presentAddressContextMenu: @escaping (ASDisplayNode) -> 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, presentAddressContextMenu: presentAddressContextMenu)
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)
@ -422,12 +424,12 @@ private enum WalletInfoListEntry: Equatable, Comparable, Identifiable {
}
}
private func preparedTransition(from fromEntries: [WalletInfoListEntry], to toEntries: [WalletInfoListEntry], account: Account, presentationData: PresentationData, action: @escaping (WalletTransaction) -> Void, presentAddressContextMenu: @escaping (ASDisplayNode) -> 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, presentAddressContextMenu: presentAddressContextMenu), 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, presentAddressContextMenu: presentAddressContextMenu), 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)
}
@ -828,20 +830,20 @@ private final class WalletInfoScreenNode: ViewControllerTracingNode {
return
}
strongSelf.openTransaction(transaction)
}, presentAddressContextMenu: { [weak self] node in
}, displayAddressContextMenu: { [weak self] node, frame in
guard let strongSelf = self else {
return
}
strongSelf.present(ContextMenuController(actions: [ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: strongSelf.presentationData.strings.Conversation_ContextMenuCopy), action: {
guard let strongSelf = self else {
return
}
UIPasteboard.general.string = strongSelf.address
})]), ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak node] in
guard let strongSelf = self, let node = node else {
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
}
return (node, node.bounds, strongSelf, strongSelf.bounds)
}))
})
self.currentEntries = updatedEntries

View file

@ -10,6 +10,8 @@ import TelegramCore
import Camera
import GlassButtonNode
import UrlHandling
import CoreImage
import AlertUI
private func generateFrameImage() -> UIImage? {
return generateImage(CGSize(width: 64.0, height: 64.0), contextGenerator: { size, context in
@ -100,14 +102,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
@ -116,6 +117,38 @@ public final class WalletQrScanScreen: ViewController {
strongSelf.completion(parsedWalletUrl)
}
})
(self.displayNode as! WalletQrScanScreenNode).presentGallery = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.context.sharedContext.openImagePicker(context: strongSelf.context, completion: { image in
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])!
if let ciImage = CIImage(image: image) {
var options: [String: Any]
if ciImage.properties.keys.contains((kCGImagePropertyOrientation as String)) {
options = [CIDetectorImageOrientation: ciImage.properties[(kCGImagePropertyOrientation as String)] ?? 1]
} else {
options = [CIDetectorImageOrientation: 1]
}
let features = detector.features(in: ciImage, options: options)
for case let row as CIQRCodeFeature in features {
guard let message = row.messageString else {
continue
}
if let url = URL(string: message), let parsedWalletUrl = parseWalletUrl(url) {
strongSelf.completion(parsedWalletUrl)
return
}
}
}
let controller = textAlertController(context: strongSelf.context, title: nil, text: "No valid QR code detected.", actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})])
strongSelf.present(controller, in: .window(.root))
}, present: { [weak self] c in
self?.push(c)
})
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@ -145,6 +178,8 @@ private final class WalletQrScanScreenNode: ViewControllerTracingNode, UIScrollV
fileprivate let focusedCode = ValuePromise<CameraCode?>(ignoreRepeated: true)
private var focusedRect: CGRect?
var presentGallery: (() -> Void)?
private var validLayout: (ContainerViewLayout, CGFloat)?
init(presentationData: PresentationData) {
@ -270,13 +305,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,13 +334,17 @@ 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)
}
@objc private func galleryPressed() {
self.presentGallery?()
}
@objc private func torchPressed() {

View file

@ -314,7 +314,11 @@ public final class WalletSplashScreen: ViewController {
guard let strongSelf = self else {
return
}
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: "https://telegram.org", forceExternal: true, presentationData: strongSelf.presentationData, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: {})
var url = strongSelf.presentationData.strings.Wallet_Intro_TermsUrl
if url.isEmpty {
url = "https://telegram.org/tos/wallet"
}
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: strongSelf.navigationController as? NavigationController, dismissInput: {})
})
self.displayNodeDidLoad()

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 {