Update API

This commit is contained in:
Ilya Laktyushin
2024-04-15 16:39:53 +04:00
parent 78b0c5f509
commit 231d8f0808
21 changed files with 1379 additions and 1050 deletions

View File

@@ -39,6 +39,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
private var signInWithAppleButton: UIControl?
private let proceedNode: SolidRoundedButtonNode
private let textField: TextFieldNode
private let textSeparatorNode: ASDisplayNode
private let codeInputView: CodeInputView
private let errorTextNode: ImmediateTextNode
@@ -64,7 +67,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var email: String?
var currentCode: String {
return self.codeInputView.text
switch self.codeType {
case .word, .phrase:
return self.textField.textField.text ?? ""
default:
return self.codeInputView.text
}
}
var loginWithCode: ((String) -> Void)?
@@ -81,6 +89,19 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var inProgress: Bool = false {
didSet {
self.codeInputView.alpha = self.inProgress ? 0.6 : 1.0
switch self.codeType {
case .word, .phrase:
if self.inProgress != oldValue {
if self.inProgress {
self.proceedNode.transitionToProgress()
} else {
self.proceedNode.transitionFromProgress()
}
}
default:
break
}
}
}
@@ -151,6 +172,28 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.codeInputView.textField.keyboardType = .numberPad
}
self.textSeparatorNode = ASDisplayNode()
self.textSeparatorNode.isLayerBacked = true
self.textSeparatorNode.backgroundColor = self.theme.list.itemPlainSeparatorColor
self.textField = TextFieldNode()
self.textField.textField.font = Font.regular(20.0)
self.textField.textField.textColor = self.theme.list.itemPrimaryTextColor
self.textField.textField.textAlignment = .natural
self.textField.textField.autocorrectionType = .yes
self.textField.textField.autocorrectionType = .no
self.textField.textField.spellCheckingType = .yes
self.textField.textField.spellCheckingType = .no
self.textField.textField.autocapitalizationType = .none
self.textField.textField.keyboardType = .default
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
self.textField.textField.textContentType = UITextContentType(rawValue: "")
}
self.textField.textField.returnKeyType = .default
self.textField.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.textField.textField.disableAutomaticKeyboardHandling = [.forward, .backward]
self.textField.textField.tintColor = self.theme.list.itemAccentColor
self.errorTextNode = ImmediateTextNode()
self.errorTextNode.alpha = 0.0
self.errorTextNode.displaysAsynchronously = false
@@ -175,11 +218,12 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.signInWithAppleButton?.isHidden = true
(self.signInWithAppleButton as? ASAuthorizationAppleIDButton)?.cornerRadius = 11
}
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_OpenFragment, theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x37475a), foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: false)
self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false)
self.proceedNode.progressType = .embedded
self.proceedNode.isHidden = true
self.proceedNode.iconSpacing = 4.0
self.proceedNode.animationSize = CGSize(width: 36.0, height: 36.0)
self.proceedNode.isEnabled = false
super.init()
@@ -189,7 +233,11 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.backgroundColor = self.theme.list.plainBackgroundColor
self.textField.textField.delegate = self
self.addSubnode(self.codeInputView)
self.addSubnode(self.textSeparatorNode)
self.addSubnode(self.textField)
self.addSubnode(self.titleNode)
self.addSubnode(self.titleActivateAreaNode)
self.addSubnode(self.titleIconNode)
@@ -208,9 +256,11 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
guard let strongSelf = self else {
return
}
strongSelf.textChanged(text: strongSelf.codeInputView.text)
strongSelf.codeChanged(text: strongSelf.codeInputView.text)
}
self.textField.textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
self.codeInputView.longPressed = { [weak self] in
guard let strongSelf = self else {
return
@@ -261,7 +311,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
func updateCode(_ code: String) {
self.codeInputView.text = code
self.textChanged(text: code)
self.codeChanged(text: code)
if let codeLength = self.requiredCodeLength, code.count == Int(codeLength) {
self.loginWithCode?(code)
@@ -401,6 +451,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
var animationName = "IntroMessage"
var animationPlaybackMode: AnimatedStickerPlaybackMode = .once
var textFieldPlaceholder = ""
if let codeType = self.codeType {
switch codeType {
case .missedCall:
@@ -412,9 +463,20 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterCodeSMSTitle, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
case .fragment:
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterCodeFragmentTitle, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
self.proceedNode.title = self.strings.Login_OpenFragment
self.proceedNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x37475a), foregroundColor: .white))
self.proceedNode.isEnabled = true
animationName = "IntroFragment"
animationPlaybackMode = .count(3)
self.proceedNode.animation = "anim_fragment"
case .word:
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterWordTitle, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
textFieldPlaceholder = self.strings.Login_EnterWordPlaceholder
case .phrase:
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterPhraseTitle, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
textFieldPlaceholder = self.strings.Login_EnterPhrasePlaceholder
default:
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterCodeTelegramTitle, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
}
@@ -422,14 +484,17 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_EnterCodeTelegramTitle, font: Font.semibold(40.0), textColor: self.theme.list.itemPrimaryTextColor)
}
self.textField.textField.placeholder = textFieldPlaceholder
self.titleActivateAreaNode.accessibilityLabel = self.titleNode.attributedText?.string ?? ""
if let inputHeight = layout.inputHeight {
if let codeType = self.codeType, case .email = codeType {
switch self.codeType {
case .email, .fragment:
insets.bottom = max(inputHeight, insets.bottom)
} else if let codeType = self.codeType, case .fragment = codeType {
insets.bottom = max(inputHeight, insets.bottom)
} else {
case .word, .phrase:
insets.bottom = max(inputHeight, layout.standardKeyboardHeight)
default:
insets.bottom = max(inputHeight, layout.standardInputHeight)
}
}
@@ -475,6 +540,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
codeLength = Int(length)
case .emailSetupRequired:
codeLength = 6
case .word, .phrase:
codeLength = 0
case .none:
codeLength = 6
}
@@ -498,8 +565,11 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
if layout.size.width > 320.0 {
items.append(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
self.animationNode.updateLayout(size: animationSize)
self.animationNode.isHidden = false
self.animationNode.visibility = true
} else {
insets.top = navigationBarHeight
self.animationNode.isHidden = true
}
var additionalBottomInset: CGFloat = 20.0
@@ -550,7 +620,19 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
}
}
items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 30.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: canReset || pendingDate != nil ? 0.0 : 104.0, maxValue: canReset ? 0.0 : 104.0)))
switch codeType {
case .word, .phrase:
self.codeInputView.isHidden = true
self.textField.isHidden = false
self.textSeparatorNode.isHidden = false
items.append(AuthorizationLayoutItem(node: self.textField, size: CGSize(width: maximumWidth - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.textSeparatorNode, size: CGSize(width: maximumWidth - 48.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
default:
self.codeInputView.isHidden = false
self.textField.isHidden = true
self.textSeparatorNode.isHidden = true
items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 30.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: canReset || pendingDate != nil ? 0.0 : 104.0, maxValue: canReset ? 0.0 : 104.0)))
}
if canReset {
self.resetNode.setAttributedTitle(NSAttributedString(string: self.strings.Login_Email_CantAccess, font: Font.regular(17.0), textColor: self.theme.list.itemAccentColor, paragraphAlignment: .center), for: [])
@@ -614,7 +696,29 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
} else {
self.signInWithAppleButton?.isHidden = true
self.dividerNode.isHidden = true
self.proceedNode.isHidden = true
switch codeType {
case .word, .phrase:
additionalBottomInset = 120.0
self.nextOptionButtonNode.isHidden = false
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
if layout.size.width > 320.0 {
self.proceedNode.isHidden = false
} else {
self.proceedNode.isHidden = true
}
let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - proceedSize.width) / 2.0), y: layout.size.height - insets.bottom - proceedSize.height - inset), size: proceedSize)
transition.updateFrame(node: self.proceedNode, frame: buttonFrame)
case .email:
self.nextOptionButtonNode.isHidden = true
self.proceedNode.isHidden = true
default:
self.nextOptionButtonNode.isHidden = false
self.proceedNode.isHidden = true
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
}
if case .email = codeType {
self.nextOptionButtonNode.isHidden = true
@@ -644,31 +748,58 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
}
func activateInput() {
let _ = self.codeInputView.becomeFirstResponder()
switch self.codeType {
case .word, .phrase:
self.textField.textField.becomeFirstResponder()
default:
let _ = self.codeInputView.becomeFirstResponder()
}
}
func animateError() {
self.codeInputView.layer.addShakeAnimation()
switch self.codeType {
case .word, .phrase:
self.textField.layer.addShakeAnimation()
default:
self.codeInputView.layer.addShakeAnimation()
}
}
func animateError(text: String) {
self.codeInputView.animateError()
self.codeInputView.layer.addShakeAnimation(amplitude: -30.0, duration: 0.5, count: 6, decay: true)
let errorOriginY: CGFloat
let errorOriginOffset: CGFloat
switch self.codeType {
case .word, .phrase:
self.textField.layer.addShakeAnimation()
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
transition.updateBackgroundColor(node: self.textSeparatorNode, color: self.theme.list.itemDestructiveColor)
errorOriginY = self.textField.frame.maxY
errorOriginOffset = 5.0
default:
self.codeInputView.animateError()
self.codeInputView.layer.addShakeAnimation(amplitude: -30.0, duration: 0.5, count: 6, decay: true)
errorOriginY = self.codeInputView.frame.maxY
errorOriginOffset = 11.0
}
self.errorTextNode.attributedText = NSAttributedString(string: text, font: Font.regular(17.0), textColor: self.theme.list.itemDestructiveColor, paragraphAlignment: .center)
self.errorTextNode.attributedText = NSAttributedString(string: text, font: Font.regular(13.0), textColor: self.theme.list.itemDestructiveColor, paragraphAlignment: .center)
if let (layout, _) = self.layoutArguments {
let errorTextSize = self.errorTextNode.updateLayout(CGSize(width: layout.size.width - 48.0, height: .greatestFiniteMagnitude))
let yOffset: CGFloat = layout.size.width > 320.0 ? 28.0 : 15.0
self.errorTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - errorTextSize.width) / 2.0), y: self.codeInputView.frame.maxY + yOffset), size: errorTextSize)
let yOffset: CGFloat = layout.size.width > 320.0 ? errorOriginOffset + 13.0 : errorOriginOffset
self.errorTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - errorTextSize.width) / 2.0), y: errorOriginY + yOffset), size: errorTextSize)
}
self.errorTextNode.alpha = 1.0
self.errorTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.errorTextNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true)
Queue.mainQueue().after(0.85) {
Queue.mainQueue().after(1.6) {
self.errorTextNode.alpha = 0.0
self.errorTextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
transition.updateBackgroundColor(node: self.textSeparatorNode, color: self.theme.list.itemPlainSeparatorColor)
}
}
@@ -678,12 +809,14 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
let values: [NSNumber] = [1.0, 1.1, 1.0]
self.codeInputView.layer.animateKeyframes(values: values, duration: 0.4, keyPath: "transform.scale")
}
@objc func codeFieldTextChanged(_ textField: UITextField) {
self.textChanged(text: textField.text ?? "")
@objc private func textDidChange() {
let text = self.textField.textField.text ?? ""
self.proceedNode.isEnabled = !text.isEmpty
self.updateNextEnabled?(!text.isEmpty)
}
private func textChanged(text: String) {
private func codeChanged(text: String) {
self.updateNextEnabled?(!text.isEmpty)
if let codeType = self.codeType {
var codeLength: Int32?
@@ -715,20 +848,36 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
if self.inProgress {
return false
}
var result = ""
for c in string {
if c.unicodeScalars.count == 1 {
let scalar = c.unicodeScalars.first!
if scalar >= "0" && scalar <= "9" {
result.append(c)
var updated = textField.text ?? ""
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
if let codeType = self.codeType {
switch codeType {
case let .word(startsWith):
if let startsWith, startsWith.count == 1, !updated.isEmpty && !updated.hasPrefix(startsWith) {
if self.errorTextNode.alpha.isZero {
//TODO:localize
self.animateError(text: "Incorrect, please try again")
}
return false
}
case let .phrase(startsWith):
if let startsWith, !updated.isEmpty {
let firstWord = updated.components(separatedBy: " ").first ?? ""
if !firstWord.isEmpty && !startsWith.hasPrefix(firstWord) {
if self.errorTextNode.alpha.isZero {
//TODO:localize
self.animateError(text: "Incorrect, please try again")
}
return false
}
}
default:
break
}
}
if result != string {
textField.text = result
self.codeFieldTextChanged(textField)
return false
}
return true
}
@@ -737,8 +886,15 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
}
@objc func proceedPressed() {
if case let .fragment(url, _) = self.codeType {
switch self.codeType {
case let .fragment(url, _):
self.openFragment?(url)
case .word, .phrase:
if let text = self.textField.textField.text, !text.isEmpty {
self.loginWithCode?(text)
}
default:
break
}
}