diff --git a/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift b/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift index b9852fc7c7..cc410c27d6 100644 --- a/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift +++ b/TelegramUI/AuthorizationSequenceAwaitingAccountResetController.swift @@ -82,5 +82,3 @@ final class AuthorizationSequenceAwaitingAccountResetController: ViewController self.logout?() } } - - diff --git a/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift b/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift index 8cd6d51a66..685c8eb4fe 100644 --- a/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift +++ b/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift @@ -1,6 +1,7 @@ import Foundation import AsyncDisplayKit import Display +import SwiftSignalKit private func timerValueString(days: Int32, hours: Int32, minutes: Int32, color: UIColor, strings: PresentationStrings) -> NSAttributedString { var string = NSMutableAttributedString() @@ -37,6 +38,8 @@ final class AuthorizationSequenceAwaitingAccountResetControllerNode: ASDisplayNo private var protectedUntil: Int32 = 0 + private var timer: SwiftSignalKit.Timer? + init(strings: PresentationStrings, theme: AuthorizationTheme) { self.strings = strings self.theme = theme @@ -60,7 +63,8 @@ final class AuthorizationSequenceAwaitingAccountResetControllerNode: ASDisplayNo self.timerValueNode.displaysAsynchronously = false self.resetNode = HighlightableButtonNode() - self.resetNode.setAttributedTitle(NSAttributedString(string: strings.Login_ResetAccountProtected_Reset, font: Font.regular(21.0), textColor: self.theme.textPlaceholderColor), for: []) + self.resetNode.setAttributedTitle(NSAttributedString(string: strings.Login_ResetAccountProtected_Reset, font: Font.regular(21.0), textColor: self.theme.accentColor), for: []) + self.resetNode.setAttributedTitle(NSAttributedString(string: strings.Login_ResetAccountProtected_Reset, font: Font.regular(21.0), textColor: self.theme.textPlaceholderColor), for: [.disabled]) self.resetNode.displaysAsynchronously = false self.resetNode.isEnabled = false @@ -81,6 +85,10 @@ final class AuthorizationSequenceAwaitingAccountResetControllerNode: ASDisplayNo self.resetNode.addTarget(self, action: #selector(self.resetPressed), forControlEvents: .touchUpInside) } + deinit { + self.timer?.invalidate() + } + func updateData(protectedUntil: Int32, number: String) { self.protectedUntil = protectedUntil self.updateTimerValue() @@ -90,10 +98,18 @@ final class AuthorizationSequenceAwaitingAccountResetControllerNode: ASDisplayNo if let (layout, navigationHeight) = self.layoutArguments { self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) } + + if self.timer == nil { + let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in + self?.updateTimerValue() + }, queue: Queue.mainQueue()) + self.timer = timer + timer.start() + } } private func updateTimerValue() { - let timerSeconds = max(0, Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - self.protectedUntil) + let timerSeconds = max(0, self.protectedUntil - Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) let secondsInAMinute: Int32 = 60 let secondsInAnHour: Int32 = 60 * secondsInAMinute @@ -112,6 +128,12 @@ final class AuthorizationSequenceAwaitingAccountResetControllerNode: ASDisplayNo } self.timerValueNode.attributedText = timerValueString(days: days, hours: hours, minutes: minutes, color: self.theme.primaryColor, strings: self.strings) + + self.resetNode.isEnabled = timerSeconds <= 0 + + if let (layout, navigationHeight) = self.layoutArguments { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) + } } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/TelegramUI/AuthorizationSequenceCodeEntryController.swift b/TelegramUI/AuthorizationSequenceCodeEntryController.swift index aa22851d01..2969c6501c 100644 --- a/TelegramUI/AuthorizationSequenceCodeEntryController.swift +++ b/TelegramUI/AuthorizationSequenceCodeEntryController.swift @@ -124,32 +124,6 @@ final class AuthorizationSequenceCodeEntryController: ViewController { } private func continueWithCode(_ code: String) { - if let (termsOfService, exclusuve) = self.termsOfService, exclusuve { - var acceptImpl: (() -> Void)? - var declineImpl: (() -> Void)? - let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(authTheme: self.theme), strings: self.strings, text: termsOfService.text, entities: termsOfService.entities, ageConfirmation: termsOfService.ageConfirmation, signingUp: true, accept: { _ in - acceptImpl?() - }, decline: { - declineImpl?() - }, openUrl: { [weak self] url in - self?.openUrl(url) - }) - acceptImpl = { [weak self, weak controller] in - controller?.dismiss() - if let strongSelf = self { - strongSelf.termsOfService = nil - strongSelf.loginWithCode?(code) - } - } - declineImpl = { [weak self, weak controller] in - controller?.dismiss() - self?.reset?() - self?.controllerNode.activateInput() - } - self.view.endEditing(true) - self.present(controller, in: .window(.root)) - } else { - self.loginWithCode?(code) - } + self.loginWithCode?(code) } } diff --git a/TelegramUI/AuthorizationSequenceController.swift b/TelegramUI/AuthorizationSequenceController.swift index 6e58f099c9..60d720b546 100644 --- a/TelegramUI/AuthorizationSequenceController.swift +++ b/TelegramUI/AuthorizationSequenceController.swift @@ -190,7 +190,100 @@ public final class AuthorizationSequenceController: NavigationController { if let strongSelf = self { controller?.inProgress = true - strongSelf.actionDisposable.set((authorizeWithCode(account: strongSelf.account, code: code, termsOfService: termsOfService?.0) |> deliverOnMainQueue).start(error: { error in + /* + if let (termsOfService, exclusuve) = self.termsOfService, exclusuve { + + var acceptImpl: (() -> Void)? + var declineImpl: (() -> Void)? + let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(authTheme: self.theme), strings: self.strings, text: termsOfService.text, entities: termsOfService.entities, ageConfirmation: termsOfService.ageConfirmation, signingUp: true, accept: { _ in + acceptImpl?() + }, decline: { + declineImpl?() + }, openUrl: { [weak self] url in + self?.openUrl(url) + }) + acceptImpl = { [weak self, weak controller] in + controller?.dismiss() + if let strongSelf = self { + strongSelf.termsOfService = nil + strongSelf.loginWithCode?(code) + } + } + declineImpl = { [weak self, weak controller] in + controller?.dismiss() + self?.reset?() + self?.controllerNode.activateInput() + } + self.view.endEditing(true) + self.present(controller, in: .window(.root)) + } else { + */ + + strongSelf.actionDisposable.set((authorizeWithCode(account: strongSelf.account, code: code, termsOfService: termsOfService?.0) + |> deliverOnMainQueue).start(next: { result in + guard let strongSelf = self else { + return + } + controller?.inProgress = false + switch result { + case let .signUp(data): + if let (termsOfService, explicit) = termsOfService, explicit { + var presentAlertAgainImpl: (() -> Void)? + let presentAlertImpl: () -> Void = { + guard let strongSelf = self else { + return + } + var dismissImpl: (() -> Void)? + let alertTheme = AlertControllerTheme(authTheme: strongSelf.theme) + let attributedText = stringWithAppliedEntities(termsOfService.text, entities: termsOfService.entities, baseColor: alertTheme.primaryColor, linkColor: alertTheme.accentColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), fixedFont: Font.regular(13.0)) + let contentNode = TextAlertContentNode(theme: alertTheme, title: NSAttributedString(string: strongSelf.strings.Login_TermsOfServiceHeader, font: Font.medium(17.0), textColor: alertTheme.primaryColor, paragraphAlignment: .center), text: attributedText, actions: [ + TextAlertAction(type: .defaultAction, title: strongSelf.strings.Login_TermsOfServiceAgree, action: { + dismissImpl?() + guard let strongSelf = self else { + return + } + let _ = beginSignUp(account: strongSelf.account, data: data).start() + }), TextAlertAction(type: .genericAction, title: strongSelf.strings.Login_TermsOfServiceDecline, action: { + dismissImpl?() + guard let strongSelf = self else { + return + } + strongSelf.window?.present(standardTextAlertController(theme: alertTheme, title: strongSelf.strings.Login_TermsOfServiceDecline, text: strongSelf.strings.Login_TermsOfServiceSignupDecline, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_Cancel, action: { + presentAlertAgainImpl?() + }), TextAlertAction(type: .genericAction, title: strongSelf.strings.Login_TermsOfServiceDecline, action: { + guard let strongSelf = self else { + return + } + let account = strongSelf.account + let _ = (strongSelf.account.postbox.transaction { transaction -> Void in + transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) + }).start() + })]), on: .root) + }) + ], actionLayout: .vertical) + contentNode.textAttributeAction = (NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), { value in + if let value = value as? String { + strongSelf.openUrl(value) + } + }) + let controller = AlertController(theme: alertTheme, contentNode: contentNode) + dismissImpl = { [weak controller] in + controller?.dismissAnimated() + } + strongSelf.view.endEditing(true) + strongSelf.window?.present(controller, on: .root) + } + presentAlertAgainImpl = { + presentAlertImpl() + } + presentAlertImpl() + } else { + let _ = beginSignUp(account: strongSelf.account, data: data).start() + } + case .loggedIn: + break + } + }, error: { error in Queue.mainQueue().async { if let strongSelf = self, let controller = controller { controller.inProgress = false @@ -356,6 +449,8 @@ public final class AuthorizationSequenceController: NavigationController { switch error { case .generic: text = strongSelf.strings.Login_UnknownError + case .limitExceeded: + text = strongSelf.strings.Login_ResetAccountProtected_LimitExceeded } strongController.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: strongSelf.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_OK, action: {})]), in: .window(.root)) } @@ -452,8 +547,10 @@ public final class AuthorizationSequenceController: NavigationController { strongController.inProgress = false let text: String switch error { - case .generic: - text = strongSelf.strings.Login_UnknownError + case .generic: + text = strongSelf.strings.Login_UnknownError + case .limitExceeded: + text = strongSelf.strings.Login_ResetAccountProtected_LimitExceeded } strongController.present(standardTextAlertController(theme: AlertControllerTheme(authTheme: strongSelf.theme), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.strings.Common_OK, action: {})]), in: .window(.root)) } diff --git a/TelegramUI/AuthorizationSequenceSignUpController.swift b/TelegramUI/AuthorizationSequenceSignUpController.swift index 6d10e8f615..cbe8b2187b 100644 --- a/TelegramUI/AuthorizationSequenceSignUpController.swift +++ b/TelegramUI/AuthorizationSequenceSignUpController.swift @@ -98,7 +98,7 @@ final class AuthorizationSequenceSignUpController: ViewController { override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition) + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } @objc func nextPressed() { diff --git a/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift b/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift index 0d7959d3ed..f3c9965895 100644 --- a/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift +++ b/TelegramUI/AuthorizationSequenceSignUpControllerNode.swift @@ -244,9 +244,9 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel let titleOffset: CGFloat if navigationHeight * 0.5 < titleSize.height + minimalTitleSpacing { - titleOffset = floor((navigationHeight - titleSize.height) / 2.0) + titleOffset = max(navigationBarHeight, floor((navigationHeight - titleSize.height) / 2.0)) } else { - titleOffset = max(navigationHeight * 0.5, navigationHeight - maxTitleSpacing - titleSize.height) + titleOffset = max(navigationBarHeight, max(navigationHeight * 0.5, navigationHeight - maxTitleSpacing - titleSize.height)) } transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: titleOffset), size: titleSize)) @@ -278,9 +278,9 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing) } - let currentOptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - noticeSize.width) / 2.0), y: lastFieldFrame.maxY + noticeSpacing), size: noticeSize) + let currentOptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - noticeSize.width) / 2.0), y: lastFieldFrame.maxY + max(0.0, noticeSpacing)), size: noticeSize) transition.updateFrame(node: self.currentOptionNode, frame: currentOptionFrame) - let termsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsSize.width) / 2.0), y: layout.size.height - insets.bottom - termsSize.height - 1.0), size: termsSize) + let termsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsSize.width) / 2.0), y: layout.size.height - insets.bottom - termsSize.height - 4.0), size: termsSize) transition.updateFrame(node: self.termsNode, frame: termsFrame) } diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index fee7430b22..9019b11b39 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -1794,6 +1794,28 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } } + self.chatDisplayNode.navigateButtons.mentionsMenu = { [weak self] in + guard let strongSelf = self else { + return + } + let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme) + actionSheet.setItemGroups([ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.WebSearch_RecentSectionClear, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { + return + } + let _ = clearPeerUnseenPersonalMessagesInteractively(account: strongSelf.account, peerId: peerId).start() + }) + ]), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.chatDisplayNode.dismissInput() + strongSelf.present(actionSheet, in: .window(.root)) + } + let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { [weak self] messageId in if let strongSelf = self, strongSelf.isNodeLoaded, canSendMessagesToChat(strongSelf.presentationInterfaceState) { if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { diff --git a/TelegramUI/ChatHistoryNavigationButtonNode.swift b/TelegramUI/ChatHistoryNavigationButtonNode.swift index 4daf40e838..78e2222320 100644 --- a/TelegramUI/ChatHistoryNavigationButtonNode.swift +++ b/TelegramUI/ChatHistoryNavigationButtonNode.swift @@ -14,7 +14,17 @@ class ChatHistoryNavigationButtonNode: ASControlNode { private let badgeBackgroundNode: ASImageNode private let badgeTextNode: ASTextNode - var tapped: (() -> Void)? + var tapped: (() -> Void)? { + didSet { + if (oldValue != nil) != (self.tapped != nil) { + if self.tapped != nil { + self.addTarget(self, action: #selector(onTap), forControlEvents: .touchUpInside) + } else { + self.removeTarget(self, action: #selector(onTap), forControlEvents: .touchUpInside) + } + } + } + } var badge: String = "" { didSet { @@ -59,8 +69,6 @@ class ChatHistoryNavigationButtonNode: ASControlNode { self.addSubnode(self.badgeTextNode) self.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0)) - - self.addTarget(self, action: #selector(onTap), forControlEvents: .touchUpInside) } func updateTheme(theme: PresentationTheme) { diff --git a/TelegramUI/ChatHistoryNavigationButtons.swift b/TelegramUI/ChatHistoryNavigationButtons.swift index c5219fc55f..fc4840a403 100644 --- a/TelegramUI/ChatHistoryNavigationButtons.swift +++ b/TelegramUI/ChatHistoryNavigationButtons.swift @@ -6,6 +6,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { private var theme: PresentationTheme private let mentionsButton: ChatHistoryNavigationButtonNode + private let mentionsButtonTapNode: ASDisplayNode private let downButton: ChatHistoryNavigationButtonNode var downPressed: (() -> Void)? { @@ -14,11 +15,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } } - var mentionsPressed: (() -> Void)? { - didSet { - self.mentionsButton.tapped = self.mentionsPressed - } - } + var mentionsPressed: (() -> Void)? + var mentionsMenu: (() -> Void)? var displayDownButton: Bool = false { didSet { @@ -57,15 +55,30 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { self.mentionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .mentions) self.mentionsButton.alpha = 0.0 + self.mentionsButtonTapNode = ASDisplayNode() + self.downButton = ChatHistoryNavigationButtonNode(theme: theme, type: .down) self.downButton.alpha = 0.0 super.init() + self.mentionsButton.isUserInteractionEnabled = false + self.addSubnode(self.mentionsButton) + self.addSubnode(self.mentionsButtonTapNode) self.addSubnode(self.downButton) } + override func didLoad() { + super.didLoad() + + let tapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.mentionsTap(_:))) + tapRecognizer.tapActionAtPoint = { _ in + return .waitForSingleTap + } + self.mentionsButtonTapNode.view.addGestureRecognizer(tapRecognizer) + } + func updateTheme(theme: PresentationTheme) { if self.theme !== theme { self.theme = theme @@ -92,14 +105,17 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { if self.mentionCount != 0 { transition.updateAlpha(node: self.mentionsButton, alpha: 1.0) transition.updateTransformScale(node: self.mentionsButton, scale: 1.0) + self.mentionsButtonTapNode.isHidden = false } else { transition.updateAlpha(node: self.mentionsButton, alpha: 0.0) transition.updateTransformScale(node: self.mentionsButton, scale: 0.2) + self.mentionsButtonTapNode.isHidden = true } transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center) transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center) + self.mentionsButtonTapNode.frame = CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize) return completeSize } @@ -107,6 +123,9 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let subnodes = self.subnodes { for subnode in subnodes { + if !subnode.isUserInteractionEnabled { + continue + } if let result = subnode.hitTest(point.offsetBy(dx: -subnode.frame.minX, dy: -subnode.frame.minY), with: event) { return result } @@ -114,4 +133,14 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } return nil } + + @objc private func mentionsTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0 { + if case .tap = gesture { + self.mentionsPressed?() + } else if case .longTap = gesture { + self.mentionsMenu?() + } + } + } } diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index 943b7a5b82..e76fc0ed9d 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -14,7 +14,7 @@ private let titleFont = Font.regular(16.0) private let descriptionFont = Font.regular(13.0) private let durationFont = Font.regular(11.0) -final class ChatMessageInteractiveFileNode: ASTransformNode { +final class ChatMessageInteractiveFileNode: ASDisplayNode { private let titleNode: TextNode private let descriptionNode: TextNode private let waveformNode: AudioWaveformNode @@ -41,7 +41,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { private var themeAndStrings: (ChatPresentationThemeData, PresentationStrings)? private var file: TelegramMediaFile? - init() { + override init() { self.titleNode = TextNode() self.titleNode.displaysAsynchronously = true self.titleNode.isLayerBacked = true @@ -59,7 +59,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { self.consumableContentNode = ASImageNode() - super.init(layerBacked: false) + super.init() self.addSubnode(self.titleNode) self.addSubnode(self.descriptionNode) diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index 8fe1c0bcab..9d2f988fd7 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -15,7 +15,7 @@ enum InteractiveMediaNodeSizeCalculation { case unconstrained } -final class ChatMessageInteractiveMediaNode: ASTransformNode { +final class ChatMessageInteractiveMediaNode: ASDisplayNode { private let imageNode: TransformImageNode private var videoNode: UniversalVideoNode? private var statusNode: RadialStatusNode? @@ -56,11 +56,11 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { var activateLocalContent: () -> Void = { } - init() { + override init() { self.imageNode = TransformImageNode() self.imageNode.contentAnimations = [.subsequentUpdates] - super.init(layerBacked: false) + super.init() self.imageNode.displaysAsynchronously = false self.addSubnode(self.imageNode) @@ -295,6 +295,9 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { if file.isVideo && file.isAnimated && !isSecretMedia && automaticPlayback { updateVideoFile = file if hasCurrentVideoNode { + if let currentFile = currentMedia as? TelegramMediaFile, currentFile.resource is EmptyMediaResource { + replaceVideoNode = true + } } else { replaceVideoNode = true } diff --git a/TelegramUI/DateFormat.swift b/TelegramUI/DateFormat.swift index de88317bc9..0d0a875c80 100644 --- a/TelegramUI/DateFormat.swift +++ b/TelegramUI/DateFormat.swift @@ -94,7 +94,8 @@ func stringForDateWithoutYear(date: Date, strings: PresentationStrings) -> Strin } func roundDateToDays(_ timestamp: Int32) -> Int32 { - let calendar = Calendar(identifier: .gregorian) + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(secondsFromGMT: 0)! var components = calendar.dateComponents(Set([.era, .year, .month, .day]), from: Date(timeIntervalSince1970: Double(timestamp))) components.hour = 0 components.minute = 0 diff --git a/TelegramUI/DateSelectionActionSheetController.swift b/TelegramUI/DateSelectionActionSheetController.swift index 916c65e53a..0ef0f72cea 100644 --- a/TelegramUI/DateSelectionActionSheetController.swift +++ b/TelegramUI/DateSelectionActionSheetController.swift @@ -89,6 +89,7 @@ private final class DateSelectionActionSheetItemNode: ActionSheetItemNode { self.valueChanged = valueChanged self.pickerView = UIDatePicker() + self.pickerView.timeZone = TimeZone(secondsFromGMT: 0) self.pickerView.datePickerMode = .date self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue))) self.pickerView.locale = localeWithStrings(strings) diff --git a/TelegramUI/EditableTokenListNode.swift b/TelegramUI/EditableTokenListNode.swift index 080d87d429..9f5ac1b914 100644 --- a/TelegramUI/EditableTokenListNode.swift +++ b/TelegramUI/EditableTokenListNode.swift @@ -145,7 +145,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { self.addSubnode(self.separatorNode) self.scrollNode.addSubnode(self.placeholderNode) self.scrollNode.addSubnode(self.textFieldNode) - self.scrollNode.addSubnode(self.caretIndicatorNode) + //self.scrollNode.addSubnode(self.caretIndicatorNode) self.clipsToBounds = true self.textFieldNode.textField.delegate = self @@ -311,15 +311,15 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { } func textFieldDidBeginEditing(_ textField: UITextField) { - if self.caretIndicatorNode.supernode == self { + /*if self.caretIndicatorNode.supernode == self { self.caretIndicatorNode.removeFromSupernode() - } + }*/ } func textFieldDidEndEditing(_ textField: UITextField) { - if self.caretIndicatorNode.supernode != self.scrollNode { + /*if self.caretIndicatorNode.supernode != self.scrollNode { self.scrollNode.addSubnode(self.caretIndicatorNode) - } + }*/ } func setText(_ text: String) { diff --git a/TelegramUI/FetchCachedRepresentations.swift b/TelegramUI/FetchCachedRepresentations.swift index dcbc9a8132..911f182234 100644 --- a/TelegramUI/FetchCachedRepresentations.swift +++ b/TelegramUI/FetchCachedRepresentations.swift @@ -54,8 +54,8 @@ private func fetchCachedStickerAJpegRepresentation(account: Account, resource: M }, scale: 1.0) if let alphaImage = alphaImage, let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, nil) - CGImageDestinationSetProperties(alphaDestination, nil) + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary) let colorQuality: Float let alphaQuality: Float @@ -119,7 +119,7 @@ private func fetchCachedScaledImageRepresentation(account: Account, resource: Me }, scale: 1.0)! if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, nil) + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.5 @@ -178,7 +178,7 @@ private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource let url = URL(fileURLWithPath: path) if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, nil) + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.6 @@ -220,7 +220,7 @@ private func fetchCachedScaledVideoFirstFrameRepresentation(account: Account, re }, scale: 1.0)! if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, nil) + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.5 diff --git a/TelegramUI/FormControllerDetailActionItem.swift b/TelegramUI/FormControllerDetailActionItem.swift index 9e67ed798f..246fa6c7ac 100644 --- a/TelegramUI/FormControllerDetailActionItem.swift +++ b/TelegramUI/FormControllerDetailActionItem.swift @@ -103,7 +103,11 @@ final class FormControllerDetailActionItemNode: FormBlockItemNode Void let returnPressed: () -> Void - init(title: String, text: String, placeholder: String, color: FormControllerTextInputItemColor = .primary, type: FormControllerTextInputItemType, error: String? = nil, textUpdated: @escaping (String) -> Void, returnPressed: @escaping () -> Void) { + init(title: String, text: String, placeholder: String, color: FormControllerTextInputItemColor = .primary, type: FormControllerTextInputItemType, returnKeyType: UIReturnKeyType = .next, error: String? = nil, textUpdated: @escaping (String) -> Void, returnPressed: @escaping () -> Void) { self.title = title self.text = text self.placeholder = placeholder self.color = color self.type = type + self.returnKeyType = returnKeyType self.error = error self.textUpdated = textUpdated self.returnPressed = returnPressed @@ -71,7 +73,6 @@ final class FormControllerTextInputItemNode: FormBlockItemNode Bool { - return true + if let recognizer = self.recognizer, otherGestureRecognizer == recognizer { + return true + } else { + return false + } } @objc func revealTapGesture(_ recognizer: UITapGestureRecognizer) { diff --git a/TelegramUI/OpenUrl.swift b/TelegramUI/OpenUrl.swift index 7ec536086e..eb29ce1a14 100644 --- a/TelegramUI/OpenUrl.swift +++ b/TelegramUI/OpenUrl.swift @@ -5,6 +5,86 @@ import TelegramCore import Postbox import SwiftSignalKit +public struct ParsedSecureIdUrl { + public let peerId: PeerId + public let scope: String + public let publicKey: String + public let callbackUrl: String + public let opaquePayload: Data + public let opaqueNonce: Data +} + +public func parseSecureIdUrl(url: URL) -> ParsedSecureIdUrl? { + guard let query = url.query else { + return nil + } + + if url.host == "passport" || url.host == "resolve" { + if let components = URLComponents(string: "/?" + query) { + var domain: String? + var botId: Int32? + var scope: String? + var publicKey: String? + var callbackUrl: String? + var opaquePayload = Data() + var opaqueNonce = Data() + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "domain" { + domain = value + } else if queryItem.name == "bot_id" { + botId = Int32(value) + } else if queryItem.name == "scope" { + scope = value + } else if queryItem.name == "public_key" { + publicKey = value + } else if queryItem.name == "callback_url" { + callbackUrl = value + } else if queryItem.name == "payload" { + if let data = value.data(using: .utf8) { + opaquePayload = data + } + } else if queryItem.name == "nonce" { + if let data = value.data(using: .utf8) { + opaqueNonce = data + } + } + } + } + } + + let valid: Bool + if url.host == "resolve" { + if domain == "telegrampassport" { + valid = true + } else { + valid = false + } + } else { + valid = true + } + + if valid { + if let botId = botId, let scope = scope, let publicKey = publicKey, let callbackUrl = callbackUrl { + if scope.hasPrefix("{") && scope.hasSuffix("}") { + opaquePayload = Data() + if opaqueNonce.isEmpty { + return nil + } + } else if opaquePayload.isEmpty { + return nil + } + + return ParsedSecureIdUrl(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: botId), scope: scope, publicKey: publicKey, callbackUrl: callbackUrl, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce) + } + } + } + } + + return nil +} + public func openExternalUrl(account: Account, url: String, forceExternal: Bool = false, presentationData: PresentationData, applicationContext: TelegramApplicationContext, navigationController: NavigationController?, dismissInput: @escaping () -> Void) { if forceExternal || url.lowercased().hasPrefix("tel:") || url.lowercased().hasPrefix("calshow:") { applicationContext.applicationBindings.openUrl(url) diff --git a/TelegramUI/SecureIdAuthController.swift b/TelegramUI/SecureIdAuthController.swift index 618b6e8cf3..f008e30f3d 100644 --- a/TelegramUI/SecureIdAuthController.swift +++ b/TelegramUI/SecureIdAuthController.swift @@ -116,7 +116,7 @@ final class SecureIdAuthController: ViewController { strongSelf.updateState(animated: true, { state in var state = state state.verificationState = .verified(context.context) - state.twoStepEmail = context.settings.email + state.twoStepEmail = !context.settings.email.isEmpty ? context.settings.email : nil switch state { case var .form(form): form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) }) @@ -420,7 +420,7 @@ final class SecureIdAuthController: ViewController { strongSelf.updateState(animated: !inBackground, { state in var state = state state.verificationState = .verified(context.context) - state.twoStepEmail = context.settings.email + state.twoStepEmail = !context.settings.email.isEmpty ? context.settings.email : nil switch state { case var .form(form): form.formData = form.encryptedFormData.flatMap({ decryptedSecureIdForm(context: context.context, form: $0.form) }) diff --git a/TelegramUI/SecureIdAuthControllerNode.swift b/TelegramUI/SecureIdAuthControllerNode.swift index f36780e528..1c6bcd5814 100644 --- a/TelegramUI/SecureIdAuthControllerNode.swift +++ b/TelegramUI/SecureIdAuthControllerNode.swift @@ -738,7 +738,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { case .temporaryRegistration: strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .temporaryRegistration, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .address: - strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: true, document: nil, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) + strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: true, document: nil, translations: false), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .utilityBill: strongSelf.interaction.present(SecureIdDocumentFormController(account: strongSelf.account, context: context, requestedData: .address(details: false, document: .utilityBill, translations: true), primaryLanguageByCountry: primaryLanguageByCountry, values: values, updatedValues: updatedValues(field)), nil) case .bankStatement: diff --git a/TelegramUI/SecureIdAuthFormFieldNode.swift b/TelegramUI/SecureIdAuthFormFieldNode.swift index 6c098dc411..5f08e86135 100644 --- a/TelegramUI/SecureIdAuthFormFieldNode.swift +++ b/TelegramUI/SecureIdAuthFormFieldNode.swift @@ -647,8 +647,7 @@ private func fieldTitleAndText(field: SecureIdParsedRequestedFormField, strings: } text.append(fieldsText(addressValue.street1, addressValue.street2, addressValue.city, addressValue.state, addressValue.postcode, countryName(code: addressValue.countryCode, strings: strings))) } - } - if let filledDocument = filledDocument, let string = stringForDocumentValue(filledDocument.1, strings: strings) { + } else if let filledDocument = filledDocument, let string = stringForDocumentValue(filledDocument.1, strings: strings) { if !text.isEmpty { text.append(", ") } @@ -834,6 +833,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { } } else { filled = false + text = strings.Passport_FieldIdentityDetailsHelp } } if let document = document { @@ -878,6 +878,7 @@ final class SecureIdAuthFormFieldNode: ASDisplayNode { if addressDetails { if findValue(values, key: .address) == nil { filled = false + text = strings.Passport_FieldAddressHelp } } if let document = document { diff --git a/TelegramUI/SecureIdDocumentFormControllerNode.swift b/TelegramUI/SecureIdDocumentFormControllerNode.swift index b7cd345468..4aa3f1973f 100644 --- a/TelegramUI/SecureIdDocumentFormControllerNode.swift +++ b/TelegramUI/SecureIdDocumentFormControllerNode.swift @@ -51,17 +51,19 @@ final class SecureIdDocumentFormParams { fileprivate let openDocument: (SecureIdVerificationDocument) -> Void fileprivate let updateText: (SecureIdDocumentFormTextField, String) -> Void fileprivate let selectNextInputItem: (SecureIdDocumentFormEntry) -> Void + fileprivate let endEditing: () -> Void fileprivate let activateSelection: (SecureIdDocumentFormSelectionField) -> Void fileprivate let scanPassport: () -> Void fileprivate let deleteValue: () -> Void - fileprivate init(account: Account, context: SecureIdAccessContext, addFile: @escaping (AddFileTarget) -> Void, openDocument: @escaping (SecureIdVerificationDocument) -> Void, updateText: @escaping (SecureIdDocumentFormTextField, String) -> Void, selectNextInputItem: @escaping (SecureIdDocumentFormEntry) -> Void, activateSelection: @escaping (SecureIdDocumentFormSelectionField) -> Void, scanPassport: @escaping () -> Void, deleteValue: @escaping () -> Void) { + fileprivate init(account: Account, context: SecureIdAccessContext, addFile: @escaping (AddFileTarget) -> Void, openDocument: @escaping (SecureIdVerificationDocument) -> Void, updateText: @escaping (SecureIdDocumentFormTextField, String) -> Void, selectNextInputItem: @escaping (SecureIdDocumentFormEntry) -> Void, endEditing: @escaping () -> Void, activateSelection: @escaping (SecureIdDocumentFormSelectionField) -> Void, scanPassport: @escaping () -> Void, deleteValue: @escaping () -> Void) { self.account = account self.context = context self.addFile = addFile self.openDocument = openDocument self.updateText = updateText self.selectNextInputItem = selectNextInputItem + self.endEditing = endEditing self.activateSelection = activateSelection self.scanPassport = scanPassport self.deleteValue = deleteValue @@ -1285,6 +1287,9 @@ extension SecureIdDocumentFormState { values[.address] = .address(SecureIdAddressValue(street1: details.street1, street2: details.street2, city: details.city, state: details.state, countryCode: details.countryCode, postcode: details.postcode)) } if let document = address.document { + guard !verificationDocuments.isEmpty else { + return nil + } switch document { case .passportRegistration: values[.passportRegistration] = .passportRegistration(SecureIdPassportRegistrationValue(verificationDocuments: verificationDocuments, translations: translationDocuments)) @@ -1809,10 +1814,10 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { params.selectNextInputItem(self) }) case let .nativeLastName(value, error): - return FormControllerTextInputItem(title: strings.Passport_Identity_Surname, text: value, placeholder: strings.Passport_Identity_SurnamePlaceholder, type: .regular(capitalization: .words, autocorrection: false), error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Identity_Surname, text: value, placeholder: strings.Passport_Identity_SurnamePlaceholder, type: .regular(capitalization: .words, autocorrection: false), returnKeyType: .`default`, error: error, textUpdated: { text in params.updateText(.nativeLastName, text) }, returnPressed: { - params.selectNextInputItem(self) + params.endEditing() }) case let .nativeInfo(language, countryCode): let text: String @@ -1922,10 +1927,10 @@ enum SecureIdDocumentFormEntry: FormControllerEntry { } else { color = .primary } - return FormControllerTextInputItem(title: strings.Passport_Address_Postcode, text: value, placeholder: strings.Passport_Address_PostcodePlaceholder, color: color, type: .latin(capitalization: .allCharacters), error: error, textUpdated: { text in + return FormControllerTextInputItem(title: strings.Passport_Address_Postcode, text: value, placeholder: strings.Passport_Address_PostcodePlaceholder, color: color, type: .latin(capitalization: .allCharacters), returnKeyType: .`default`, error: error, textUpdated: { text in params.updateText(.postcode, text) }, returnPressed: { - params.selectNextInputItem(self) + params.endEditing() }) case .requestedDocumentsHeader: return FormControllerHeaderItem(text: strings.Passport_Identity_FilesTitle) @@ -2150,13 +2155,26 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode Void = { [weak controller] in controller?.dismissAnimated() } + + let text: String + let title: String + switch innerState.documentState { + case let .identity(state) where state.details != nil: + text = self.strings.Passport_DeletePersonalDetailsConfirmation + title = self.strings.Passport_DeletePersonalDetails + case let .address(state) where state.details != nil: + text = self.strings.Passport_DeleteAddressConfirmation + title = self.strings.Passport_DeleteAddress + default: + text = self.strings.Passport_DeleteDocumentConfirmation + title = self.strings.Passport_DeleteDocument + } + controller.setItemGroups([ ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: self.strings.Passport_DeleteDocumentConfirmation), - ActionSheetButtonItem(title: strings.Passport_DeleteDocument, color: .destructive, action: { [weak self] in + ActionSheetTextItem(title: text), + ActionSheetButtonItem(title: title, color: .destructive, action: { [weak self] in dismissAction() guard let strongSelf = self else { return diff --git a/TelegramUI/SecureIdPlaintextFormControllerNode.swift b/TelegramUI/SecureIdPlaintextFormControllerNode.swift index 8fadb6a633..99cb4f1d0b 100644 --- a/TelegramUI/SecureIdPlaintextFormControllerNode.swift +++ b/TelegramUI/SecureIdPlaintextFormControllerNode.swift @@ -914,10 +914,12 @@ final class SecureIdPlaintextFormControllerNode: FormControllerNode