Various improvements

This commit is contained in:
Ilya Laktyushin 2022-08-21 14:38:59 +03:00
parent f7a9728ad6
commit e5a4f5f107
30 changed files with 1068 additions and 276 deletions

View File

@ -7973,5 +7973,7 @@ Sorry for the inconvenience.";
"Settings.ChangeProfilePhoto" = "Change Profile Photo"; "Settings.ChangeProfilePhoto" = "Change Profile Photo";
"Premium.EmojiStatusTitle" = "This is %1$@'s current status from %2$@."; "Premium.EmojiStatusTitle" = "This is %1$@'s current status from #[%2$@]().";
"Premium.EmojiStatusText" = "Emoji status is a premium feature.\n Other features included in **Telegram Premium**:"; "Premium.EmojiStatusText" = "Emoji status is a premium feature.\n Other features included in **Telegram Premium**:";
"Login.SelectCountry" = "Country";

View File

@ -780,7 +780,7 @@ public enum PremiumIntroSource {
case about case about
case deeplink(String?) case deeplink(String?)
case profile(PeerId) case profile(PeerId)
case emojiStatus(PeerId, Int64) case emojiStatus(PeerId, Int64, TelegramMediaFile?, String?)
} }
#if ENABLE_WALLET #if ENABLE_WALLET

View File

@ -7,26 +7,32 @@ import TextFormat
import Markdown import Markdown
public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> NSAttributedString { public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> NSAttributedString {
let fontSize: CGFloat = 17.0
switch type { switch type {
case .sms: case .sms:
return NSAttributedString(string: strings.Login_CodeSentSms, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) return NSAttributedString(string: strings.Login_CodeSentSms, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
case .otherSession: case .otherSession:
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor) let body = MarkdownAttributeSet(font: Font.regular(fontSize), textColor: primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor) let bold = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: primaryColor)
return parseMarkdownIntoAttributedString(strings.Login_CodeSentInternal, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center) return parseMarkdownIntoAttributedString(strings.Login_CodeSentInternal, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)
case .missedCall: case .missedCall:
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor) let body = MarkdownAttributeSet(font: Font.regular(fontSize), textColor: primaryColor)
let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor) let bold = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: primaryColor)
return parseMarkdownIntoAttributedString(strings.Login_ShortCallTitle, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center) return parseMarkdownIntoAttributedString(strings.Login_ShortCallTitle, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil }), textAlignment: .center)
case .call: case .call:
return NSAttributedString(string: strings.Login_CodeSentCall, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) return NSAttributedString(string: strings.Login_CodeSentCall, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
case .flashCall: case .flashCall:
return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) return NSAttributedString(string: strings.ChangePhoneNumberCode_Called, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
case .emailSetupRequired: case .emailSetupRequired:
return NSAttributedString(string: "", font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) return NSAttributedString(string: "", font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center)
case let .email(emailPattern, _, _, _, _): case let .email(emailPattern, _, _, _, _):
//TODO: localize //TODO: localize
return NSAttributedString(string: "Please enter the code we have sent to your email \(emailPattern).", font: Font.regular(16.0), textColor: primaryColor, paragraphAlignment: .center) let mutableString = NSAttributedString(string: "Please enter the code we have sent to your email \(emailPattern).", font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center).mutableCopy() as! NSMutableAttributedString
let range = (mutableString.string as NSString).range(of: "*******")
if range.location != NSNotFound {
mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: range)
}
return mutableString
} }
} }

View File

@ -1524,7 +1524,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peer = messages.last?.author { if let peer = messages.last?.author {
if case let .user(user) = peer, let emojiStatus = user.emojiStatus { if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
currentCredibilityIconContent = .emojiStatus(status: emojiStatus, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor) currentCredibilityIconContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
} else if peer.isScam { } else if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor) currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
@ -1545,7 +1545,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer { } else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if case let .user(user) = peer, let emojiStatus = user.emojiStatus { if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
currentCredibilityIconContent = .emojiStatus(status: emojiStatus, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor) currentCredibilityIconContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
} else if peer.isScam { } else if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor) currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)

View File

@ -48,28 +48,38 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func didLoad() {
super.didLoad()
self.textNode.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0)
}
func update(borderColor: UInt32, isHighlighted: Bool) { func update(borderColor: UInt32, isHighlighted: Bool) {
if self.borderColorValue != borderColor { if self.borderColorValue != borderColor {
self.borderColorValue = borderColor self.borderColorValue = borderColor
let previousColor = self.backgroundView.layer.borderColor let previousColor = self.backgroundView.layer.borderColor
self.backgroundView.layer.cornerRadius = 5.0 self.backgroundView.layer.cornerRadius = 15.0
if #available(iOS 13.0, *) {
self.backgroundView.layer.cornerCurve = .continuous
}
self.backgroundView.layer.borderColor = UIColor(argb: borderColor).cgColor self.backgroundView.layer.borderColor = UIColor(argb: borderColor).cgColor
self.backgroundView.layer.borderWidth = 1.0 self.backgroundView.layer.borderWidth = 1.0 + UIScreenPixel
if let previousColor = previousColor { if let previousColor = previousColor {
self.backgroundView.layer.animate(from: previousColor, to: UIColor(argb: borderColor).cgColor, keyPath: "borderColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.15) self.backgroundView.layer.animate(from: previousColor, to: UIColor(argb: borderColor).cgColor, keyPath: "borderColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.15)
} }
} }
} }
func update(textColor: UInt32, text: String, size: CGSize, animated: Bool) { func update(textColor: UInt32, text: String, size: CGSize, fontSize: CGFloat, animated: Bool) {
let previousText = self.text let previousText = self.text
self.text = text self.text = text
if animated && previousText.isEmpty != text.isEmpty { if animated && previousText.isEmpty != text.isEmpty {
if !text.isEmpty { if !text.isEmpty {
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
self.textNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: 0.0, y: size.height / 2.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, additive: true) self.textNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: 0.0, y: size.height * 0.35)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, damping: 70.0, additive: true)
self.textNode.layer.animateScaleY(from: 0.01, to: 1.0, duration: 0.25)
} else { } else {
if let copyView = self.textNode.view.snapshotContentTree() { if let copyView = self.textNode.view.snapshotContentTree() {
self.view.insertSubview(copyView, at: 0) self.view.insertSubview(copyView, at: 0)
@ -81,8 +91,6 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
} }
} }
let fontSize: CGFloat = floor(21.0 * size.height / 28.0)
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
self.textNode.attributedText = NSAttributedString(string: text, font: UIFont.monospacedSystemFont(ofSize: fontSize, weight: .regular), textColor: UIColor(argb: textColor)) self.textNode.attributedText = NSAttributedString(string: text, font: UIFont.monospacedSystemFont(ofSize: fontSize, weight: .regular), textColor: UIColor(argb: textColor))
} else { } else {
@ -105,6 +113,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
private var theme: Theme? private var theme: Theme?
private var count: Int? private var count: Int?
private var prefix: String = ""
private var textValue: String = "" private var textValue: String = ""
public var text: String { public var text: String {
@ -215,6 +224,15 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
let itemView = self.itemViews[i] let itemView = self.itemViews[i]
let itemSize = itemView.bounds.size let itemSize = itemView.bounds.size
let fontSize: CGFloat
if self.prefix.isEmpty {
let height: CGFloat = 51.0
fontSize = floor(13.0 * height / 28.0)
} else {
let height: CGFloat = 28.0
fontSize = floor(21.0 * height / 28.0)
}
itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder, isHighlighted: self.focusIndex == i) itemView.update(borderColor: self.focusIndex == i ? theme.activeBorder : theme.inactiveBorder, isHighlighted: self.focusIndex == i)
let itemText: String let itemText: String
if i < self.textValue.count { if i < self.textValue.count {
@ -222,13 +240,14 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
} else { } else {
itemText = "" itemText = ""
} }
itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, animated: animated) itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, fontSize: fontSize, animated: animated)
} }
} }
public func update(theme: Theme, prefix: String, count: Int, width: CGFloat) -> CGSize { public func update(theme: Theme, prefix: String, count: Int, width: CGFloat) -> CGSize {
self.theme = theme self.theme = theme
self.count = count self.count = count
self.prefix = prefix
if theme.isDark { if theme.isDark {
self.textField.keyboardAppearance = .dark self.textField.keyboardAppearance = .dark
@ -236,11 +255,14 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
self.textField.keyboardAppearance = .light self.textField.keyboardAppearance = .light
} }
let fontSize: CGFloat
let height: CGFloat let height: CGFloat
if prefix.isEmpty { if prefix.isEmpty {
height = 40.0 height = 51.0
fontSize = floor(13.0 * height / 28.0)
} else { } else {
height = 28.0 height = 28.0
fontSize = floor(21.0 * height / 28.0)
} }
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
@ -251,8 +273,8 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.0)) let prefixSize = self.prefixLabel.updateLayout(CGSize(width: width, height: 100.0))
let prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0 let prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0
let itemSize = CGSize(width: floor(25.0 * height / 28.0), height: height) let itemSize = CGSize(width: floor(24.0 * height / 28.0), height: height)
let itemSpacing: CGFloat = 5.0 let itemSpacing: CGFloat = prefix.isEmpty ? 15.0 : 5.0
let itemsWidth: CGFloat = itemSize.width * CGFloat(count) + itemSpacing * CGFloat(count - 1) let itemsWidth: CGFloat = itemSize.width * CGFloat(count) + itemSpacing * CGFloat(count - 1)
let contentWidth: CGFloat = prefixSize.width + prefixSpacing + itemsWidth let contentWidth: CGFloat = prefixSize.width + prefixSpacing + itemsWidth
@ -276,7 +298,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
} else { } else {
itemText = "" itemText = ""
} }
itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, animated: false) itemView.update(textColor: theme.foreground, text: itemText, size: itemSize, fontSize: fontSize, animated: false)
itemView.frame = CGRect(origin: CGPoint(x: contentOriginX + prefixSize.width + prefixSpacing + CGFloat(i) * (itemSize.width + itemSpacing), y: 0.0), size: itemSize) itemView.frame = CGRect(origin: CGPoint(x: contentOriginX + prefixSize.width + prefixSpacing + CGFloat(i) * (itemSize.width + itemSpacing), y: 0.0), size: itemSize)
} }
if self.itemViews.count > count { if self.itemViews.count > count {

View File

@ -0,0 +1,26 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "MultilineTextWithEntitiesComponent",
module_name = "MultilineTextWithEntitiesComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display:Display",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/Markdown:Markdown",
"//submodules/TextFormat:TextFormat",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,211 @@
import Foundation
import UIKit
import ComponentFlow
import Display
import Markdown
import TextNodeWithEntities
import AccountContext
import AnimationCache
import MultiAnimationRenderer
public final class MultilineTextWithEntitiesComponent: Component {
public enum TextContent: Equatable {
case plain(NSAttributedString)
case markdown(text: String, attributes: MarkdownAttributes)
}
public let context: AccountContext?
public let animationCache: AnimationCache?
public let animationRenderer: MultiAnimationRenderer?
public let placeholderColor: UIColor?
public let text: TextContent
public let horizontalAlignment: NSTextAlignment
public let verticalAlignment: TextVerticalAlignment
public let truncationType: CTLineTruncationType
public let maximumNumberOfLines: Int
public let lineSpacing: CGFloat
public let cutout: TextNodeCutout?
public let insets: UIEdgeInsets
public let textShadowColor: UIColor?
public let textStroke: (UIColor, CGFloat)?
public let highlightColor: UIColor?
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
public init(
context: AccountContext?,
animationCache: AnimationCache?,
animationRenderer: MultiAnimationRenderer?,
placeholderColor: UIColor?,
text: TextContent,
horizontalAlignment: NSTextAlignment = .natural,
verticalAlignment: TextVerticalAlignment = .top,
truncationType: CTLineTruncationType = .end,
maximumNumberOfLines: Int = 1,
lineSpacing: CGFloat = 0.0,
cutout: TextNodeCutout? = nil,
insets: UIEdgeInsets = UIEdgeInsets(),
textShadowColor: UIColor? = nil,
textStroke: (UIColor, CGFloat)? = nil,
highlightColor: UIColor? = nil,
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil,
longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
) {
self.context = context
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.placeholderColor = placeholderColor
self.text = text
self.horizontalAlignment = horizontalAlignment
self.verticalAlignment = verticalAlignment
self.truncationType = truncationType
self.maximumNumberOfLines = maximumNumberOfLines
self.lineSpacing = lineSpacing
self.cutout = cutout
self.insets = insets
self.textShadowColor = textShadowColor
self.textStroke = textStroke
self.highlightColor = highlightColor
self.highlightAction = highlightAction
self.tapAction = tapAction
self.longTapAction = longTapAction
}
public static func ==(lhs: MultilineTextWithEntitiesComponent, rhs: MultilineTextWithEntitiesComponent) -> Bool {
if lhs.text != rhs.text {
return false
}
if lhs.horizontalAlignment != rhs.horizontalAlignment {
return false
}
if lhs.verticalAlignment != rhs.verticalAlignment {
return false
}
if lhs.truncationType != rhs.truncationType {
return false
}
if lhs.maximumNumberOfLines != rhs.maximumNumberOfLines {
return false
}
if lhs.lineSpacing != rhs.lineSpacing {
return false
}
if lhs.cutout != rhs.cutout {
return false
}
if lhs.insets != rhs.insets {
return false
}
if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor {
if !lhsTextShadowColor.isEqual(rhsTextShadowColor) {
return false
}
} else if (lhs.textShadowColor != nil) != (rhs.textShadowColor != nil) {
return false
}
if let lhsTextStroke = lhs.textStroke, let rhsTextStroke = rhs.textStroke {
if !lhsTextStroke.0.isEqual(rhsTextStroke.0) {
return false
}
if lhsTextStroke.1 != rhsTextStroke.1 {
return false
}
} else if (lhs.textShadowColor != nil) != (rhs.textShadowColor != nil) {
return false
}
if let lhsHighlightColor = lhs.highlightColor, let rhsHighlightColor = rhs.highlightColor {
if !lhsHighlightColor.isEqual(rhsHighlightColor) {
return false
}
} else if (lhs.highlightColor != nil) != (rhs.highlightColor != nil) {
return false
}
return true
}
public final class View: UIView {
let textNode: ImmediateTextNodeWithEntities
public override init(frame: CGRect) {
self.textNode = ImmediateTextNodeWithEntities()
super.init(frame: frame)
self.addSubview(self.textNode.view)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func update(component: MultilineTextWithEntitiesComponent, availableSize: CGSize, transition: Transition) -> CGSize {
let attributedString: NSAttributedString
switch component.text {
case let .plain(string):
attributedString = string
case let .markdown(text, attributes):
attributedString = parseMarkdownIntoAttributedString(text, attributes: attributes)
}
let previousText = self.textNode.attributedText?.string
self.textNode.attributedText = attributedString
self.textNode.maximumNumberOfLines = component.maximumNumberOfLines
self.textNode.truncationType = component.truncationType
self.textNode.textAlignment = component.horizontalAlignment
self.textNode.verticalAlignment = component.verticalAlignment
self.textNode.lineSpacing = component.lineSpacing
self.textNode.cutout = component.cutout
self.textNode.insets = component.insets
self.textNode.textShadowColor = component.textShadowColor
self.textNode.textStroke = component.textStroke
self.textNode.linkHighlightColor = component.highlightColor
self.textNode.highlightAttributeAction = component.highlightAction
self.textNode.tapAttributeAction = component.tapAction
self.textNode.longTapAttributeAction = component.longTapAction
if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string {
if let snapshotView = self.snapshotView(afterScreenUpdates: false) {
snapshotView.center = self.center
self.superview?.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
}
}
let size = self.textNode.updateLayout(availableSize)
self.textNode.frame = CGRect(origin: .zero, size: size)
self.textNode.visibility = true
if let context = component.context, let animationCache = component.animationCache, let animationRenderer = component.animationRenderer, let placeholderColor = component.placeholderColor {
self.textNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: animationCache,
renderer: animationRenderer,
placeholderColor: placeholderColor,
attemptSynchronous: false
)
}
return size
}
}
public func makeView() -> View {
return View()
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -155,6 +155,10 @@ public extension ContainerViewLayout {
return self.size.width > self.size.height ? .landscape : .portrait return self.size.width > self.size.height ? .landscape : .portrait
} }
var standardKeyboardHeight: CGFloat {
return self.deviceMetrics.keyboardHeight(inLandscape: self.orientation == .landscape)
}
var standardInputHeight: CGFloat { var standardInputHeight: CGFloat {
return self.deviceMetrics.keyboardHeight(inLandscape: self.orientation == .landscape) + self.deviceMetrics.predictiveInputHeight(inLandscape: self.orientation == .landscape) return self.deviceMetrics.keyboardHeight(inLandscape: self.orientation == .landscape) + self.deviceMetrics.predictiveInputHeight(inLandscape: self.orientation == .landscape)
} }

View File

@ -165,19 +165,11 @@ public struct Font {
} }
public static func medium(_ size: CGFloat) -> UIFont { public static func medium(_ size: CGFloat) -> UIFont {
if #available(iOS 8.2, *) { return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.medium)
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.medium)
} else {
return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil)
}
} }
public static func semibold(_ size: CGFloat) -> UIFont { public static func semibold(_ size: CGFloat) -> UIFont {
if #available(iOS 8.2, *) { return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.semibold)
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.semibold)
} else {
return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil)
}
} }
public static func bold(_ size: CGFloat) -> UIFont { public static func bold(_ size: CGFloat) -> UIFont {
@ -188,17 +180,12 @@ public struct Font {
} }
} }
public static func heavy(_ size: CGFloat) -> UIFont { public static func heavy(_ size: CGFloat) -> UIFont {
return self.with(size: size, design: .regular, weight: .heavy, traits: []) return self.with(size: size, design: .regular, weight: .heavy, traits: [])
} }
public static func light(_ size: CGFloat) -> UIFont { public static func light(_ size: CGFloat) -> UIFont {
if #available(iOS 8.2, *) { return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.light)
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.light)
} else {
return CTFontCreateWithName("HelveticaNeue-Light" as CFString, size, nil)
}
} }
public static func semiboldItalic(_ size: CGFloat) -> UIFont { public static func semiboldItalic(_ size: CGFloat) -> UIFont {

View File

@ -8,20 +8,12 @@ import GradientBackground
private let regularTitleFont = Font.regular(36.0) private let regularTitleFont = Font.regular(36.0)
private let regularSubtitleFont: UIFont = { private let regularSubtitleFont: UIFont = {
if #available(iOS 8.2, *) { return UIFont.systemFont(ofSize: 10.0, weight: UIFont.Weight.bold)
return UIFont.systemFont(ofSize: 10.0, weight: UIFont.Weight.bold)
} else {
return CTFontCreateWithName("HelveticaNeue-Bold" as CFString, 10.0, nil)
}
}() }()
private let largeTitleFont = Font.regular(40.0) private let largeTitleFont = Font.regular(40.0)
private let largeSubtitleFont: UIFont = { private let largeSubtitleFont: UIFont = {
if #available(iOS 8.2, *) { return UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.bold)
return UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.bold)
} else {
return CTFontCreateWithName("HelveticaNeue-Bold" as CFString, 12.0, nil)
}
}() }()
private func generateButtonImage(background: PasscodeBackground, frame: CGRect, title: String, subtitle: String, highlighted: Bool) -> UIImage? { private func generateButtonImage(background: PasscodeBackground, frame: CGRect, title: String, subtitle: String, highlighted: Bool) -> UIImage? {

View File

@ -177,7 +177,7 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
private func updatePlaceholder() { private func updatePlaceholder() {
if let mask = self.mask { if let mask = self.mask {
let mutableMask = NSMutableAttributedString(attributedString: mask) let mutableMask = NSMutableAttributedString(attributedString: mask)
mutableMask.replaceCharacters(in: NSRange(location: 0, length: mask.string.count), with: mask.string.replacingOccurrences(of: "X", with: "")) mutableMask.replaceCharacters(in: NSRange(location: 0, length: mask.string.count), with: mask.string.replacingOccurrences(of: "X", with: "0"))
if let text = self.numberField.textField.text { if let text = self.numberField.textField.text {
mutableMask.replaceCharacters(in: NSRange(location: 0, length: min(text.count, mask.string.count)), with: text) mutableMask.replaceCharacters(in: NSRange(location: 0, length: min(text.count, mask.string.count)), with: text)
} }

View File

@ -76,12 +76,6 @@ swift_library(
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/ReactionSelectionNode:ReactionSelectionNode", "//submodules/ReactionSelectionNode:ReactionSelectionNode",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
"//submodules/Components/SheetComponent:SheetComponent",
"//submodules/Components/BundleIconComponent:BundleIconComponent",
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",
"//submodules/InAppPurchaseManager:InAppPurchaseManager", "//submodules/InAppPurchaseManager:InAppPurchaseManager",
"//submodules/ConfettiEffect:ConfettiEffect", "//submodules/ConfettiEffect:ConfettiEffect",
"//submodules/TextFormat:TextFormat", "//submodules/TextFormat:TextFormat",
@ -93,6 +87,14 @@ swift_library(
"//submodules/ShimmerEffect:ShimmerEffect", "//submodules/ShimmerEffect:ShimmerEffect",
"//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyComponents:LegacyComponents",
"//submodules/CheckNode:CheckNode", "//submodules/CheckNode:CheckNode",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent:MultilineTextWithEntitiesComponent",
"//submodules/Components/SheetComponent:SheetComponent",
"//submodules/Components/BundleIconComponent:BundleIconComponent",
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -0,0 +1,322 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import SceneKit
import GZip
import AppBundle
import LegacyComponents
import AvatarNode
import AccountContext
import TelegramCore
import AnimationCache
import MultiAnimationRenderer
import EmojiStatusComponent
private let sceneVersion: Int = 3
class EmojiHeaderComponent: Component {
let context: AccountContext
let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer
let placeholderColor: UIColor
let fileId: Int64
let isVisible: Bool
let hasIdleAnimations: Bool
init(
context: AccountContext,
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
placeholderColor: UIColor,
fileId: Int64,
isVisible: Bool,
hasIdleAnimations: Bool
) {
self.context = context
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.placeholderColor = placeholderColor
self.fileId = fileId
self.isVisible = isVisible
self.hasIdleAnimations = hasIdleAnimations
}
static func ==(lhs: EmojiHeaderComponent, rhs: EmojiHeaderComponent) -> Bool {
return lhs.placeholderColor == rhs.placeholderColor && lhs.fileId == rhs.fileId && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations
}
final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
final class Tag {
}
func matches(tag: Any) -> Bool {
if let _ = tag as? Tag {
return true
}
return false
}
private var _ready = Promise<Bool>()
var ready: Signal<Bool, NoError> {
return self._ready.get()
}
weak var animateFrom: UIView?
weak var containerView: UIView?
var animationColor: UIColor?
private let sceneView: SCNView
let statusView: ComponentHostView<Empty>
private var previousInteractionTimestamp: Double = 0.0
private var timer: SwiftSignalKit.Timer?
private var hasIdleAnimations = false
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0)))
self.sceneView.backgroundColor = .clear
self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
self.statusView = ComponentHostView<Empty>()
super.init(frame: frame)
self.addSubview(self.sceneView)
self.addSubview(self.statusView)
self.setup()
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
self.addGestureRecognizer(tapGestureRecoginzer)
self.disablesInteractiveModalDismiss = true
self.disablesInteractiveTransitionGestureRecognizer = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.timer?.invalidate()
}
private let hapticFeedback = HapticFeedback()
private var delayTapsTill: Double?
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
self.playAppearanceAnimation(velocity: nil, mirror: false, explode: true)
}
private func setup() {
guard let url = getAppBundle().url(forResource: "gift", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else {
return
}
self.sceneView.scene = scene
self.sceneView.delegate = self
let _ = self.sceneView.snapshot()
}
private var didSetReady = false
func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
if !self.didSetReady {
self.didSetReady = true
Queue.mainQueue().justDispatch {
self._ready.set(.single(true))
self.onReady()
}
}
}
private func maybeAnimateIn() {
guard let animateFrom = self.animateFrom, var containerView = self.containerView else {
return
}
containerView = containerView.subviews[2].subviews[1]
let initialPosition = self.statusView.center
let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView)
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: 0.0, dy: -20.0)
containerView.addSubview(self.statusView)
self.statusView.center = targetPosition
animateFrom.alpha = 0.0
self.statusView.layer.animateScale(from: 0.05, to: 1.0, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring)
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
self.addSubview(self.statusView)
self.statusView.center = initialPosition
})
Queue.mainQueue().after(0.4, {
animateFrom.alpha = 1.0
})
self.animateFrom = nil
self.containerView = nil
}
private func onReady() {
self.setupScaleAnimation()
self.maybeAnimateIn()
self.playAppearanceAnimation(explode: true)
self.previousInteractionTimestamp = CACurrentMediaTime()
self.timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
if let strongSelf = self, strongSelf.hasIdleAnimations {
let currentTimestamp = CACurrentMediaTime()
if currentTimestamp > strongSelf.previousInteractionTimestamp + 5.0 {
strongSelf.playAppearanceAnimation()
}
}
}, queue: Queue.mainQueue())
self.timer?.start()
}
private func setupScaleAnimation() {
// let animation = CABasicAnimation(keyPath: "transform.scale")
// animation.duration = 2.0
// animation.fromValue = 1.0
// animation.toValue = 1.15
// animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
// animation.autoreverses = true
// animation.repeatCount = .infinity
//
// self.avatarNode.view.layer.add(animation, forKey: "scale")
}
private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) {
guard let scene = self.sceneView.scene else {
return
}
let currentTime = CACurrentMediaTime()
self.previousInteractionTimestamp = currentTime
self.delayTapsTill = currentTime + 0.85
if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) {
if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first {
leftParticleSystem.speedFactor = 2.0
leftParticleSystem.particleVelocity = 1.6
leftParticleSystem.birthRate = 60.0
leftParticleSystem.particleLifeSpan = 4.0
rightParticleSystem.speedFactor = 2.0
rightParticleSystem.particleVelocity = 1.6
rightParticleSystem.birthRate = 60.0
rightParticleSystem.particleLifeSpan = 4.0
// leftBottomParticleSystem.speedFactor = 2.0
leftBottomParticleSystem.particleVelocity = 1.6
leftBottomParticleSystem.birthRate = 24.0
leftBottomParticleSystem.particleLifeSpan = 7.0
// rightBottomParticleSystem.speedFactor = 2.0
rightBottomParticleSystem.particleVelocity = 1.6
rightBottomParticleSystem.birthRate = 24.0
rightBottomParticleSystem.particleLifeSpan = 7.0
node.physicsField?.isActive = true
Queue.mainQueue().after(1.0) {
node.physicsField?.isActive = false
leftParticleSystem.birthRate = 12.0
leftParticleSystem.particleVelocity = 1.2
leftParticleSystem.particleLifeSpan = 3.0
rightParticleSystem.birthRate = 12.0
rightParticleSystem.particleVelocity = 1.2
rightParticleSystem.particleLifeSpan = 3.0
leftBottomParticleSystem.particleVelocity = 1.2
leftBottomParticleSystem.birthRate = 7.0
leftBottomParticleSystem.particleLifeSpan = 5.0
rightBottomParticleSystem.particleVelocity = 1.2
rightBottomParticleSystem.birthRate = 7.0
rightBottomParticleSystem.particleLifeSpan = 5.0
let leftAnimation = POPBasicAnimation()
leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in
property?.readBlock = { particleSystem, values in
values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor
}
property?.writeBlock = { particleSystem, values in
(particleSystem as! SCNParticleSystem).speedFactor = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
leftAnimation.fromValue = 1.2 as NSNumber
leftAnimation.toValue = 0.85 as NSNumber
leftAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
leftAnimation.duration = 0.5
leftParticleSystem.pop_add(leftAnimation, forKey: "speedFactor")
let rightAnimation = POPBasicAnimation()
rightAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in
property?.readBlock = { particleSystem, values in
values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor
}
property?.writeBlock = { particleSystem, values in
(particleSystem as! SCNParticleSystem).speedFactor = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
rightAnimation.fromValue = 1.2 as NSNumber
rightAnimation.toValue = 0.85 as NSNumber
rightAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
rightAnimation.duration = 0.5
rightParticleSystem.pop_add(rightAnimation, forKey: "speedFactor")
}
}
}
}
func update(component: EmojiHeaderComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0))
if self.sceneView.superview == self {
self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)
}
self.hasIdleAnimations = component.hasIdleAnimations
let size = self.statusView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: component.context,
animationCache: component.animationCache,
animationRenderer: component.animationRenderer,
content: .emojiStatus(
status: PeerEmojiStatus(fileId: component.fileId),
size: CGSize(width: 100.0, height: 100.0),
placeholderColor: component.placeholderColor
),
action: nil,
longTapAction: nil
)),
environment: {},
containerSize: CGSize(width: 96.0, height: 96.0)
)
self.statusView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: 63.0), size: size)
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -13,41 +13,6 @@ import TelegramCore
private let sceneVersion: Int = 3 private let sceneVersion: Int = 3
private func deg2rad(_ number: Float) -> Float {
return number * .pi / 180
}
private func rad2deg(_ number: Float) -> Float {
return number * 180.0 / .pi
}
private func generateParticlesTexture() -> UIImage {
return UIImage()
}
private func generateFlecksTexture() -> UIImage {
return UIImage()
}
private func generateShineTexture() -> UIImage {
return UIImage()
}
private func generateDiffuseTexture() -> UIImage {
return generateImage(CGSize(width: 256, height: 256), rotatedContext: { size, context in
let colorsArray: [CGColor] = [
UIColor(rgb: 0x0079ff).cgColor,
UIColor(rgb: 0x6a93ff).cgColor,
UIColor(rgb: 0x9172fe).cgColor,
UIColor(rgb: 0xe46acd).cgColor,
]
var locations: [CGFloat] = [0.0, 0.25, 0.5, 0.75, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
})!
}
class GiftAvatarComponent: Component { class GiftAvatarComponent: Component {
let context: AccountContext let context: AccountContext
let peer: EnginePeer? let peer: EnginePeer?
@ -174,8 +139,8 @@ class GiftAvatarComponent: Component {
private func setupScaleAnimation() { private func setupScaleAnimation() {
let animation = CABasicAnimation(keyPath: "transform.scale") let animation = CABasicAnimation(keyPath: "transform.scale")
animation.duration = 2.0 animation.duration = 2.0
animation.fromValue = 1.0 //NSValue(scnVector3: SCNVector3(x: 0.1, y: 0.1, z: 0.1)) animation.fromValue = 1.0
animation.toValue = 1.15 //NSValue(scnVector3: SCNVector3(x: 0.115, y: 0.115, z: 0.115)) animation.toValue = 1.15
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
animation.autoreverses = true animation.autoreverses = true
animation.repeatCount = .infinity animation.repeatCount = .infinity

View File

@ -11,6 +11,7 @@ import ViewControllerComponent
import AccountContext import AccountContext
import SolidRoundedButtonComponent import SolidRoundedButtonComponent
import MultilineTextComponent import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import BundleIconComponent import BundleIconComponent
import SolidRoundedButtonComponent import SolidRoundedButtonComponent
import Markdown import Markdown
@ -20,6 +21,8 @@ import TextFormat
import InstantPageCache import InstantPageCache
import UniversalMediaPlayer import UniversalMediaPlayer
import CheckNode import CheckNode
import AnimationCache
import MultiAnimationRenderer
public enum PremiumSource: Equatable { public enum PremiumSource: Equatable {
case settings case settings
@ -40,7 +43,7 @@ public enum PremiumSource: Equatable {
case animatedEmoji case animatedEmoji
case deeplink(String?) case deeplink(String?)
case profile(PeerId) case profile(PeerId)
case emojiStatus(PeerId, Int64) case emojiStatus(PeerId, Int64, TelegramMediaFile?, String?)
case gift(from: PeerId, to: PeerId, duration: Int32) case gift(from: PeerId, to: PeerId, duration: Int32)
case giftTerms case giftTerms
@ -158,7 +161,7 @@ enum PremiumPerk: CaseIterable {
case .animatedUserpics: case .animatedUserpics:
return "animated_userpics" return "animated_userpics"
case .appIcons: case .appIcons:
return "app_icon" return "app_icons"
case .animatedEmoji: case .animatedEmoji:
return "animated_emoji" return "animated_emoji"
} }
@ -1121,6 +1124,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0 size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0
let textColor = theme.list.itemPrimaryTextColor let textColor = theme.list.itemPrimaryTextColor
let accentColor = theme.list.itemAccentColor
let titleColor = theme.list.itemPrimaryTextColor let titleColor = theme.list.itemPrimaryTextColor
let subtitleColor = theme.list.itemSecondaryTextColor let subtitleColor = theme.list.itemSecondaryTextColor
let arrowColor = theme.list.disclosureArrowColor let arrowColor = theme.list.disclosureArrowColor
@ -1130,7 +1134,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let textString: String let textString: String
if case .emojiStatus = context.component.source { if case .emojiStatus = context.component.source {
textString = strings.Premium_EmojiStatusText textString = strings.Premium_EmojiStatusText.replacingOccurrences(of: "#", with: "# ")
} else if case .giftTerms = context.component.source { } else if case .giftTerms = context.component.source {
textString = strings.Premium_PersonalDescription textString = strings.Premium_PersonalDescription
} else if let _ = context.component.otherPeerName { } else if let _ = context.component.otherPeerName {
@ -1149,9 +1153,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
textString = strings.Premium_Description textString = strings.Premium_Description
} }
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { _ in
return nil return nil
}) })
let text = text.update( let text = text.update(
component: MultilineTextComponent( component: MultilineTextComponent(
text: .markdown( text: .markdown(
@ -1485,9 +1490,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
return nil return nil
} }
}, },
tapAction: { attributes, _ in tapAction: { [weak environment] attributes, _ in
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
if url.hasPrefix("https://apps.apple.com/account/subscriptions") { if url.hasPrefix("https://apps.apple.com/account/subscriptions") {
controller.context.sharedContext.applicationBindings.openSubscriptions() controller.context.sharedContext.applicationBindings.openSubscriptions()
} else if url.hasPrefix("https://") || url.hasPrefix("tg://") { } else if url.hasPrefix("https://") || url.hasPrefix("tg://") {
@ -1636,6 +1641,14 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
var isPremium: Bool? var isPremium: Bool?
var otherPeerName: String? var otherPeerName: String?
let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer
var emojiFile: TelegramMediaFile?
var emojiPackTitle: String?
private var emojiFileDisposable: Disposable?
private var disposable: Disposable? private var disposable: Disposable?
private var paymentDisposable = MetaDisposable() private var paymentDisposable = MetaDisposable()
private var activationDisposable = MetaDisposable() private var activationDisposable = MetaDisposable()
@ -1654,6 +1667,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.present = present self.present = present
self.completion = completion self.completion = completion
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
return TempBox.shared.tempFile(fileName: "file").path
})
self.animationRenderer = MultiAnimationRendererImpl()
super.init() super.init()
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError> let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
@ -1676,7 +1694,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|> map { peer -> String? in |> map { peer -> String? in
return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
} }
} else if case let .emojiStatus(peerId, _) = source { } else if case let .emojiStatus(peerId, _, _, _) = source {
otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> map { peer -> String? in |> map { peer -> String? in
return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
@ -1704,12 +1722,30 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
strongSelf.updated(transition: .immediate) strongSelf.updated(transition: .immediate)
} }
}) })
if case let .emojiStatus(_, emojiFileId, emojiFile, emojiPackTitle) = source {
if let emojiFile = emojiFile {
self.emojiFile = emojiFile
self.emojiPackTitle = emojiPackTitle
self.updated(transition: .immediate)
} else {
self.emojiFileDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId])
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
strongSelf.emojiFile = result[emojiFileId]
strongSelf.updated(transition: .immediate)
})
}
}
} }
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
self.paymentDisposable.dispose() self.paymentDisposable.dispose()
self.activationDisposable.dispose() self.activationDisposable.dispose()
self.emojiFileDisposable?.dispose()
} }
func buy() { func buy() {
@ -1830,10 +1866,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let background = Child(Rectangle.self) let background = Child(Rectangle.self)
let scrollContent = Child(ScrollComponent<EnvironmentType>.self) let scrollContent = Child(ScrollComponent<EnvironmentType>.self)
let star = Child(PremiumStarComponent.self) let star = Child(PremiumStarComponent.self)
let emoji = Child(EmojiHeaderComponent.self)
let topPanel = Child(BlurredRectangle.self) let topPanel = Child(BlurredRectangle.self)
let topSeparator = Child(Rectangle.self) let topSeparator = Child(Rectangle.self)
let title = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self)
let secondaryTitle = Child(MultilineTextComponent.self) let secondaryTitle = Child(MultilineTextWithEntitiesComponent.self)
let bottomPanel = Child(BlurredRectangle.self) let bottomPanel = Child(BlurredRectangle.self)
let bottomSeparator = Child(Rectangle.self) let bottomSeparator = Child(Rectangle.self)
let button = Child(SolidRoundedButtonComponent.self) let button = Child(SolidRoundedButtonComponent.self)
@ -1854,11 +1891,32 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
isIntro = false isIntro = false
} }
let star = star.update( let header: _UpdatedChildComponent
component: PremiumStarComponent(isIntro: isIntro, isVisible: starIsVisible, hasIdleAnimations: state.hasIdleAnimations), if case let .emojiStatus(_, fileId, _, _) = context.component.source {
availableSize: CGSize(width: min(390.0, context.availableSize.width), height: 220.0), header = emoji.update(
transition: context.transition component: EmojiHeaderComponent(
) context: context.component.context,
animationCache: state.animationCache,
animationRenderer: state.animationRenderer,
placeholderColor: environment.theme.list.mediaPlaceholderColor,
fileId: fileId,
isVisible: starIsVisible,
hasIdleAnimations: state.hasIdleAnimations
),
availableSize: CGSize(width: min(390.0, context.availableSize.width), height: 220.0),
transition: context.transition
)
} else {
header = star.update(
component: PremiumStarComponent(
isIntro: isIntro,
isVisible: starIsVisible,
hasIdleAnimations: state.hasIdleAnimations
),
availableSize: CGSize(width: min(390.0, context.availableSize.width), height: 220.0),
transition: context.transition
)
}
let topPanel = topPanel.update( let topPanel = topPanel.update(
component: BlurredRectangle( component: BlurredRectangle(
@ -1899,7 +1957,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
) )
let textColor = environment.theme.list.itemPrimaryTextColor let textColor = environment.theme.list.itemPrimaryTextColor
let accentColor = UIColor(rgb: 0x597cf5) let accentColor: UIColor
if case .emojiStatus = context.component.source {
accentColor = environment.theme.list.itemAccentColor
} else {
accentColor = UIColor(rgb: 0x597cf5)
}
let textFont = Font.bold(18.0) let textFont = Font.bold(18.0)
let boldTextFont = Font.bold(18.0) let boldTextFont = Font.bold(18.0)
@ -1908,10 +1971,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
return nil return nil
}) })
var highlightableLinks = false
let secondaryTitleText: String let secondaryTitleText: String
if let otherPeerName = state.otherPeerName { if let otherPeerName = state.otherPeerName {
if case .emojiStatus = context.component.source { if case let .emojiStatus(_, _, _, emojiPackTitle) = context.component.source {
secondaryTitleText = environment.strings.Premium_EmojiStatusTitle(otherPeerName, "").string highlightableLinks = true
secondaryTitleText = environment.strings.Premium_EmojiStatusTitle(otherPeerName, emojiPackTitle ?? "").string.replacingOccurrences(of: "#", with: " # ")
} else if case .profile = context.component.source { } else if case .profile = context.component.source {
secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string
} else if case let .gift(fromPeerId, _, duration) = context.component.source { } else if case let .gift(fromPeerId, _, duration) = context.component.source {
@ -1943,13 +2008,47 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
secondaryTitleText = "" secondaryTitleText = ""
} }
let secondaryAttributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(secondaryTitleText, attributes: markdownAttributes))
if let emojiFile = state.emojiFile {
let range = (secondaryAttributedText.string as NSString).range(of: "#")
if range.location != NSNotFound {
secondaryAttributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range)
}
}
let accountContext = context.component.context
let presentController = context.component.present
let secondaryTitle = secondaryTitle.update( let secondaryTitle = secondaryTitle.update(
component: MultilineTextComponent( component: MultilineTextWithEntitiesComponent(
text: .markdown(text: secondaryTitleText, attributes: markdownAttributes), context: context.component.context,
animationCache: context.state.animationCache,
animationRenderer: context.state.animationRenderer,
placeholderColor: environment.theme.list.mediaPlaceholderColor,
text: .plain(secondaryAttributedText),
horizontalAlignment: .center, horizontalAlignment: .center,
truncationType: .end, truncationType: .end,
maximumNumberOfLines: 2, maximumNumberOfLines: 2,
lineSpacing: 0.0 lineSpacing: 0.0,
highlightAction: highlightableLinks ? { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
} : nil,
tapAction: { [weak state, weak environment] _, _ in
if let emojiFile = state?.emojiFile, let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
for attribute in emojiFile.attributes {
if case let .CustomEmoji(_, _, packReference) = attribute, let packReference = packReference {
let controller = accountContext.sharedContext.makeStickerPackScreen(context: accountContext, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: navigationController, sendSticker: { _, _, _ in
return false
})
presentController(controller)
break
}
}
}
}
), ),
availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.width), availableSize: CGSize(width: context.availableSize.width - 32.0, height: context.availableSize.width),
transition: context.transition transition: context.transition
@ -2035,8 +2134,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
titleAlpha = state.otherPeerName != nil ? 0.0 : 1.0 titleAlpha = state.otherPeerName != nil ? 0.0 : 1.0
} }
context.add(star context.add(header
.position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + star.size.height / 2.0 - 30.0 - titleOffset * titleScale)) .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale))
.scale(titleScale) .scale(titleScale)
) )
@ -2251,6 +2350,19 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
self.didSetReady = true self.didSetReady = true
self._ready.set(view.ready) self._ready.set(view.ready)
if let sourceView = self.sourceView {
view.animateFrom = sourceView
view.containerView = self.containerView
view.animationColor = self.animationColor
self.sourceView = nil
self.containerView = nil
self.animationColor = nil
}
} else if let view = self.node.hostView.findTaggedView(tag: EmojiHeaderComponent.View.Tag()) as? EmojiHeaderComponent.View {
self.didSetReady = true
self._ready.set(view.ready)
if let sourceView = self.sourceView { if let sourceView = self.sourceView {
view.animateFrom = sourceView view.animateFrom = sourceView
view.containerView = self.containerView view.containerView = self.containerView

View File

@ -163,6 +163,8 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
} }
} }
public var progressType: SolidRoundedButtonProgressType = .fullSize
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) { public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
self.theme = theme self.theme = theme
self.font = font self.font = font
@ -570,43 +572,91 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
rotationAnimation.duration = 1.0
rotationAnimation.fromValue = NSNumber(value: Float(0.0))
rotationAnimation.toValue = NSNumber(value: Float.pi * 2.0)
rotationAnimation.repeatCount = Float.infinity
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
rotationAnimation.beginTime = 1.0
let buttonOffset = self.buttonBackgroundNode.frame.minX let buttonOffset = self.buttonBackgroundNode.frame.minX
let buttonWidth = self.buttonBackgroundNode.frame.width let buttonWidth = self.buttonBackgroundNode.frame.width
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - self.buttonHeight) / 2.0), y: 0.0), size: CGSize(width: self.buttonHeight, height: self.buttonHeight))
let progressNode = ASImageNode() let progressNode = ASImageNode()
progressNode.displaysAsynchronously = false
progressNode.frame = progressFrame switch self.progressType {
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel) case .fullSize:
self.insertSubnode(progressNode, at: 0) let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - self.buttonHeight) / 2.0), y: 0.0), size: CGSize(width: self.buttonHeight, height: self.buttonHeight))
progressNode.frame = progressFrame
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
self.buttonBackgroundNode.layer.cornerRadius = self.buttonHeight / 2.0
self.buttonBackgroundNode.layer.animate(from: self.buttonCornerRadius as NSNumber, to: self.buttonHeight / 2.0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
self.buttonBackgroundNode.layer.animateFrame(from: self.buttonBackgroundNode.frame, to: progressFrame, duration: 0.2)
self.buttonBackgroundNode.alpha = 0.0
self.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.insertSubnode(progressNode, at: 0)
case .embedded:
let diameter: CGFloat = self.buttonHeight - 22.0
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - diameter) / 2.0), y: floorToScreenPixels((self.buttonHeight - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter))
progressNode.frame = progressFrame
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.theme.foregroundColor, diameter: diameter, lineWidth: 3.0)
self.addSubnode(progressNode)
}
progressNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
progressNode.layer.add(rotationAnimation, forKey: "progressRotation")
self.progressNode = progressNode self.progressNode = progressNode
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
basicAnimation.duration = 0.5
basicAnimation.fromValue = NSNumber(value: Float(0.0))
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
basicAnimation.repeatCount = Float.infinity
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
basicAnimation.beginTime = 1.0
progressNode.layer.add(basicAnimation, forKey: "progressRotation")
self.buttonBackgroundNode.cornerRadius = self.buttonHeight / 2.0
self.buttonBackgroundNode.layer.animate(from: self.buttonCornerRadius as NSNumber, to: self.buttonHeight / 2.0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
self.buttonBackgroundNode.layer.animateFrame(from: self.buttonBackgroundNode.frame, to: progressFrame, duration: 0.2)
self.buttonBackgroundNode.alpha = 0.0
self.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2, removeOnCompletion: false)
progressNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
self.titleNode.alpha = 0.0 self.titleNode.alpha = 0.0
self.titleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2) self.titleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
self.subtitleNode.alpha = 0.0 self.subtitleNode.alpha = 0.0
self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2) self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
self.shimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.borderShimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
} }
public func transitionFromProgress() {
guard let progressNode = self.progressNode else {
return
}
self.progressNode = nil
switch self.progressType {
case .fullSize:
self.buttonBackgroundNode.layer.cornerRadius = self.buttonCornerRadius
self.buttonBackgroundNode.layer.animate(from: self.buttonHeight / 2.0 as NSNumber, to: self.buttonCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
self.buttonBackgroundNode.layer.animateFrame(from: progressNode.frame, to: self.bounds, duration: 0.2)
self.buttonBackgroundNode.alpha = 1.0
self.buttonBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
case .embedded:
break
}
progressNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak progressNode, weak self] _ in
progressNode?.removeFromSupernode()
self?.isUserInteractionEnabled = true
})
self.titleNode.alpha = 1.0
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.subtitleNode.alpha = 1.0
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.shimmerView?.layer.removeAllAnimations()
self.shimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.borderShimmerView?.layer.removeAllAnimations()
self.borderShimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} }
public final class SolidRoundedButtonView: UIView { public final class SolidRoundedButtonView: UIView {

View File

@ -21,7 +21,7 @@ public final class EmojiStatusComponent: Component {
case verified(fillColor: UIColor, foregroundColor: UIColor) case verified(fillColor: UIColor, foregroundColor: UIColor)
case fake(color: UIColor) case fake(color: UIColor)
case scam(color: UIColor) case scam(color: UIColor)
case emojiStatus(status: PeerEmojiStatus, placeholderColor: UIColor) case emojiStatus(status: PeerEmojiStatus, size: CGSize, placeholderColor: UIColor)
} }
public let context: AccountContext public let context: AccountContext
@ -30,6 +30,7 @@ public final class EmojiStatusComponent: Component {
public let content: Content public let content: Content
public let action: (() -> Void)? public let action: (() -> Void)?
public let longTapAction: (() -> Void)? public let longTapAction: (() -> Void)?
public let emojiFileUpdated: ((TelegramMediaFile?) -> Void)?
public init( public init(
context: AccountContext, context: AccountContext,
@ -37,7 +38,8 @@ public final class EmojiStatusComponent: Component {
animationRenderer: MultiAnimationRenderer, animationRenderer: MultiAnimationRenderer,
content: Content, content: Content,
action: (() -> Void)?, action: (() -> Void)?,
longTapAction: (() -> Void)? longTapAction: (() -> Void)?,
emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil
) { ) {
self.context = context self.context = context
self.animationCache = animationCache self.animationCache = animationCache
@ -45,6 +47,7 @@ public final class EmojiStatusComponent: Component {
self.content = content self.content = content
self.action = action self.action = action
self.longTapAction = longTapAction self.longTapAction = longTapAction
self.emojiFileUpdated = emojiFileUpdated
} }
public static func ==(lhs: EmojiStatusComponent, rhs: EmojiStatusComponent) -> Bool { public static func ==(lhs: EmojiStatusComponent, rhs: EmojiStatusComponent) -> Bool {
@ -105,6 +108,7 @@ public final class EmojiStatusComponent: Component {
var iconImage: UIImage? var iconImage: UIImage?
var emojiFileId: Int64? var emojiFileId: Int64?
var emojiPlaceholderColor: UIColor? var emojiPlaceholderColor: UIColor?
var emojiSize = CGSize()
/* /*
if case .fake = credibilityIcon { if case .fake = credibilityIcon {
@ -213,12 +217,13 @@ public final class EmojiStatusComponent: Component {
iconImage = nil iconImage = nil
case .scam: case .scam:
iconImage = nil iconImage = nil
case let .emojiStatus(emojiStatus, placeholderColor): case let .emojiStatus(emojiStatus, size, placeholderColor):
iconImage = nil iconImage = nil
emojiFileId = emojiStatus.fileId emojiFileId = emojiStatus.fileId
emojiPlaceholderColor = placeholderColor emojiPlaceholderColor = placeholderColor
emojiSize = size
if case let .emojiStatus(previousEmojiStatus, _) = self.component?.content { if case let .emojiStatus(previousEmojiStatus, _, _) = self.component?.content {
if previousEmojiStatus.fileId != emojiStatus.fileId { if previousEmojiStatus.fileId != emojiStatus.fileId {
self.emojiFileDisposable?.dispose() self.emojiFileDisposable?.dispose()
self.emojiFileDisposable = nil self.emojiFileDisposable = nil
@ -237,9 +242,10 @@ public final class EmojiStatusComponent: Component {
} }
} else { } else {
iconImage = self.iconView?.image iconImage = self.iconView?.image
if case let .emojiStatus(status, placeholderColor) = component.content { if case let .emojiStatus(status, size, placeholderColor) = component.content {
emojiFileId = status.fileId emojiFileId = status.fileId
emojiPlaceholderColor = placeholderColor emojiPlaceholderColor = placeholderColor
emojiSize = size
} }
} }
@ -273,6 +279,7 @@ public final class EmojiStatusComponent: Component {
} }
} }
let emojiFileUpdated = component.emojiFileUpdated
if let emojiFileId = emojiFileId, let emojiPlaceholderColor = emojiPlaceholderColor { if let emojiFileId = emojiFileId, let emojiPlaceholderColor = emojiPlaceholderColor {
size = availableSize size = availableSize
@ -292,7 +299,7 @@ public final class EmojiStatusComponent: Component {
cache: component.animationCache, cache: component.animationCache,
renderer: component.animationRenderer, renderer: component.animationRenderer,
placeholderColor: emojiPlaceholderColor, placeholderColor: emojiPlaceholderColor,
pointSize: CGSize(width: 32.0, height: 32.0) pointSize: emojiSize
) )
self.animationLayer = animationLayer self.animationLayer = animationLayer
self.layer.addSublayer(animationLayer) self.layer.addSublayer(animationLayer)
@ -311,10 +318,17 @@ public final class EmojiStatusComponent: Component {
} }
strongSelf.emojiFile = result[emojiFileId] strongSelf.emojiFile = result[emojiFileId]
strongSelf.state?.updated(transition: .immediate) strongSelf.state?.updated(transition: .immediate)
emojiFileUpdated?(result[emojiFileId])
}) })
} }
} }
} else { } else {
if let _ = self.emojiFile {
self.emojiFile = nil
emojiFileUpdated?(nil)
}
self.emojiFileDisposable?.dispose() self.emojiFileDisposable?.dispose()
self.emojiFileDisposable = nil self.emojiFileDisposable = nil

View File

@ -30,12 +30,12 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
var inProgress: Bool = false { var inProgress: Bool = false {
didSet { didSet {
if self.inProgress { // if self.inProgress {
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor)) // let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor))
self.navigationItem.rightBarButtonItem = item // self.navigationItem.rightBarButtonItem = item
} else { // } else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) // self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
} // }
self.controllerNode.inProgress = self.inProgress self.controllerNode.inProgress = self.inProgress
} }
} }

View File

@ -13,6 +13,7 @@ import PhoneNumberFormat
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import SolidRoundedButtonNode import SolidRoundedButtonNode
import InvisibleInkDustNode
final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate { final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
private let strings: PresentationStrings private let strings: PresentationStrings
@ -21,7 +22,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
private let animationNode: AnimatedStickerNode private let animationNode: AnimatedStickerNode
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let titleIconNode: ASImageNode private let titleIconNode: ASImageNode
private let currentOptionNode: ASTextNode private let currentOptionNode: ImmediateTextNode
private var dustNode: InvisibleInkDustNode?
private let currentOptionInfoNode: ASTextNode private let currentOptionInfoNode: ASTextNode
private let nextOptionTitleNode: ImmediateTextNode private let nextOptionTitleNode: ImmediateTextNode
private let nextOptionButtonNode: HighlightableButtonNode private let nextOptionButtonNode: HighlightableButtonNode
@ -84,9 +87,11 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.titleIconNode.displayWithoutProcessing = true self.titleIconNode.displayWithoutProcessing = true
self.titleIconNode.displaysAsynchronously = false self.titleIconNode.displaysAsynchronously = false
self.currentOptionNode = ASTextNode() self.currentOptionNode = ImmediateTextNode()
self.currentOptionNode.isUserInteractionEnabled = false self.currentOptionNode.isUserInteractionEnabled = false
self.currentOptionNode.displaysAsynchronously = false self.currentOptionNode.displaysAsynchronously = false
self.currentOptionNode.lineSpacing = 0.1
self.currentOptionNode.maximumNumberOfLines = 0
self.currentOptionInfoNode = ASTextNode() self.currentOptionInfoNode = ASTextNode()
self.currentOptionInfoNode.isUserInteractionEnabled = false self.currentOptionInfoNode.isUserInteractionEnabled = false
@ -122,23 +127,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
(self.signInWithAppleButton as? ASAuthorizationAppleIDButton)?.cornerRadius = 11 (self.signInWithAppleButton as? ASAuthorizationAppleIDButton)?.cornerRadius = 11
} }
/*self.codeField = TextFieldNode()
self.codeField.textField.font = Font.regular(24.0)
self.codeField.textField.textAlignment = .center
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
self.codeField.textField.keyboardType = .asciiCapableNumberPad
} else {
self.codeField.textField.keyboardType = .numberPad
}
if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
self.codeField.textField.textContentType = .oneTimeCode
}
self.codeField.textField.returnKeyType = .done
self.codeField.textField.textColor = self.theme.list.itemPrimaryTextColor
self.codeField.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
self.codeField.textField.disableAutomaticKeyboardHandling = [.forward, .backward]
self.codeField.textField.tintColor = self.theme.list.itemAccentColor*/
super.init() super.init()
self.setViewBlock({ self.setViewBlock({
@ -165,11 +153,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
strongSelf.textChanged(text: strongSelf.codeInputView.text) strongSelf.textChanged(text: strongSelf.codeInputView.text)
} }
//self.codeField.textField.delegate = self
//self.codeField.textField.addTarget(self, action: #selector(self.codeFieldTextChanged(_:)), for: .editingChanged)
//self.codeField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_Code, font: Font.regular(24.0), textColor: self.theme.list.itemPlaceholderTextColor)
self.nextOptionButtonNode.addTarget(self, action: #selector(self.nextOptionNodePressed), forControlEvents: .touchUpInside) self.nextOptionButtonNode.addTarget(self, action: #selector(self.nextOptionNodePressed), forControlEvents: .touchUpInside)
self.signInWithAppleButton?.addTarget(self, action: #selector(self.signInWithApplePressed), for: .touchUpInside) self.signInWithAppleButton?.addTarget(self, action: #selector(self.signInWithApplePressed), for: .touchUpInside)
} }
@ -189,8 +172,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
func updateCode(_ code: String) { func updateCode(_ code: String) {
self.codeInputView.text = code self.codeInputView.text = code
self.textChanged(text: code) self.textChanged(text: code)
//self.codeField.textField.text = code
//self.codeFieldTextChanged(self.codeField.textField)
if let codeType = self.codeType { if let codeType = self.codeType {
var codeLength: Int32? var codeLength: Int32?
switch codeType { switch codeType {
@ -228,9 +210,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor)
if case .missedCall = codeType { if case .missedCall = codeType {
self.currentOptionInfoNode.attributedText = NSAttributedString(string: self.strings.Login_CodePhonePatternInfoText, font: Font.regular(16.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) self.currentOptionInfoNode.attributedText = NSAttributedString(string: self.strings.Login_CodePhonePatternInfoText, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
} else { } else {
self.currentOptionInfoNode.attributedText = NSAttributedString(string: "", font: Font.regular(15.0), textColor: self.theme.list.itemPrimaryTextColor) self.currentOptionInfoNode.attributedText = NSAttributedString(string: "", font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor)
} }
if let timeout = timeout { if let timeout = timeout {
#if DEBUG #if DEBUG
@ -272,12 +254,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.layoutArguments = (layout, navigationBarHeight) self.layoutArguments = (layout, navigationBarHeight)
var insets = layout.insets(options: []) var insets = layout.insets(options: [])
insets.top = navigationBarHeight insets.top = layout.statusBarHeight ?? 20.0
if let inputHeight = layout.inputHeight {
insets.bottom += max(inputHeight, layout.standardInputHeight)
}
var animationName = "IntroMessage" var animationName = "IntroMessage"
if let codeType = self.codeType { if let codeType = self.codeType {
@ -296,15 +273,23 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.semibold(40.0), textColor: self.theme.list.itemPrimaryTextColor) self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.semibold(40.0), textColor: self.theme.list.itemPrimaryTextColor)
} }
if let inputHeight = layout.inputHeight {
if let codeType = self.codeType, case .email = codeType {
insets.bottom = max(inputHeight, insets.bottom)
} else {
insets.bottom = max(inputHeight, layout.standardInputHeight)
}
}
if !self.animationNode.visibility { if !self.animationNode.visibility {
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true self.animationNode.visibility = true
} }
let animationSize = CGSize(width: 88.0, height: 88.0) let animationSize = CGSize(width: 88.0, height: 88.0)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
let currentOptionSize = self.currentOptionNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude)) let currentOptionSize = self.currentOptionNode.updateLayout(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
let currentOptionInfoSize = self.currentOptionInfoNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude)) let currentOptionInfoSize = self.currentOptionInfoNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
let nextOptionSize = self.nextOptionTitleNode.updateLayout(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) let nextOptionSize = self.nextOptionTitleNode.updateLayout(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
@ -347,9 +332,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
) )
var items: [AuthorizationLayoutItem] = [] var items: [AuthorizationLayoutItem] = []
items.append(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.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.updateLayout(size: animationSize)
var additionalBottomInset: CGFloat = 20.0
if let codeType = self.codeType { if let codeType = self.codeType {
switch codeType { switch codeType {
case .otherSession: case .otherSession:
@ -421,22 +407,25 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
default: default:
self.titleIconNode.isHidden = true self.titleIconNode.isHidden = true
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 30.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 104.0, maxValue: 104.0)))
/*items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) /*items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))*/ items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))*/
if self.appleSignInAllowed, let signInWithAppleButton = self.signInWithAppleButton { if self.appleSignInAllowed, let signInWithAppleButton = self.signInWithAppleButton {
additionalBottomInset = 80.0
self.nextOptionButtonNode.isHidden = true self.nextOptionButtonNode.isHidden = true
signInWithAppleButton.isHidden = false signInWithAppleButton.isHidden = false
let dividerSize = self.dividerNode.updateLayout(width: layout.size.width) let inset: CGFloat = 24.0
items.append(AuthorizationLayoutItem(node: self.dividerNode, size: dividerSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) let buttonSize = CGSize(width: layout.size.width - inset * 2.0, height: 50.0)
transition.updateFrame(view: signInWithAppleButton, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - buttonSize.width) / 2.0), y: layout.size.height - insets.bottom - buttonSize.height - inset), size: buttonSize))
let buttonSize = CGSize(width: layout.size.width - 48.0, height: 50.0) let dividerSize = self.dividerNode.updateLayout(width: layout.size.width)
items.append(AuthorizationLayoutItem(view: signInWithAppleButton, size: buttonSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) transition.updateFrame(node: self.dividerNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - dividerSize.width) / 2.0), y: layout.size.height - insets.bottom - buttonSize.height - inset - dividerSize.height), size: dividerSize))
} else { } else {
self.signInWithAppleButton?.isHidden = true self.signInWithAppleButton?.isHidden = true
self.dividerNode.isHidden = true self.dividerNode.isHidden = true
@ -450,13 +439,28 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.codeInputView, size: codeFieldSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
/*items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 88.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))*/
items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.nextOptionButtonNode, size: nextOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 50.0, maxValue: 120.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
} }
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 20.0)), items: items, transition: transition, failIfDoesNotFit: false) let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - additionalBottomInset)), items: items, transition: transition, failIfDoesNotFit: false)
if let textLayout = self.currentOptionNode.cachedLayout, !textLayout.spoilers.isEmpty {
if self.dustNode == nil {
let dustNode = InvisibleInkDustNode(textNode: nil)
self.dustNode = dustNode
self.currentOptionNode.supernode?.insertSubnode(dustNode, aboveSubnode: self.currentOptionNode)
}
if let dustNode = self.dustNode {
let textFrame = self.currentOptionNode.frame
dustNode.update(size: textFrame.size, color: self.theme.list.itemSecondaryTextColor, textColor: self.theme.list.itemPrimaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
transition.updateFrame(node: dustNode, frame: textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0))
}
} else if let dustNode = self.dustNode {
self.dustNode = nil
dustNode.removeFromSupernode()
}
self.nextOptionTitleNode.frame = self.nextOptionButtonNode.bounds self.nextOptionTitleNode.frame = self.nextOptionButtonNode.bounds
} }

View File

@ -84,7 +84,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
self.theme = theme self.theme = theme
self.animationNode = DefaultAnimatedStickerNodeImpl() self.animationNode = DefaultAnimatedStickerNodeImpl()
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroMail"), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroMail"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true self.animationNode.visibility = true
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
@ -95,6 +95,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
self.noticeNode = ASTextNode() self.noticeNode = ASTextNode()
self.noticeNode.isUserInteractionEnabled = false self.noticeNode.isUserInteractionEnabled = false
self.noticeNode.displaysAsynchronously = false self.noticeNode.displaysAsynchronously = false
self.noticeNode.lineSpacing = 0.1
self.noticeNode.attributedText = NSAttributedString(string: "Please enter your valid email address to protect your account.", font: Font.regular(16.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) self.noticeNode.attributedText = NSAttributedString(string: "Please enter your valid email address to protect your account.", font: Font.regular(16.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
@ -186,13 +187,13 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
self.layoutArguments = (layout, navigationBarHeight) self.layoutArguments = (layout, navigationBarHeight)
var insets = layout.insets(options: []) var insets = layout.insets(options: [])
insets.top = navigationBarHeight insets.top = layout.statusBarHeight ?? 20.0
if let inputHeight = layout.inputHeight { if let inputHeight = layout.inputHeight {
insets.bottom += max(inputHeight, layout.standardInputHeight) insets.bottom += max(inputHeight, insets.bottom)
} }
self.titleNode.attributedText = NSAttributedString(string: "Add Email", font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor) self.titleNode.attributedText = NSAttributedString(string: "Add Email", font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
let animationSize = CGSize(width: 88.0, height: 88.0) let animationSize = CGSize(width: 88.0, height: 88.0)
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
@ -202,11 +203,11 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
let proceedSize = CGSize(width: layout.size.width - 48.0, height: proceedHeight) let proceedSize = CGSize(width: layout.size.width - 48.0, height: proceedHeight)
var items: [AuthorizationLayoutItem] = [] var items: [AuthorizationLayoutItem] = []
items.append(AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.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.updateLayout(size: animationSize)
items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 20.0, maxValue: 20.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 32.0, maxValue: 60.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 32.0, maxValue: 60.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 48.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 48.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
@ -221,7 +222,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
items.append(AuthorizationLayoutItem(node: self.proceedNode, size: proceedSize, spacingBefore: self.dividerNode.isHidden ? AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0) : AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.proceedNode, size: proceedSize, spacingBefore: self.dividerNode.isHidden ? AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0) : AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)))
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 20.0)), items: items, transition: transition, failIfDoesNotFit: false) let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 80.0)), items: items, transition: transition, failIfDoesNotFit: false)
if let signInWithAppleButton = self.signInWithAppleButton, self.appleSignInAllowed { if let signInWithAppleButton = self.signInWithAppleButton, self.appleSignInAllowed {
signInWithAppleButton.isHidden = false signInWithAppleButton.isHidden = false

View File

@ -50,7 +50,8 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
self.noticeNode = ASTextNode() self.noticeNode = ASTextNode()
self.noticeNode.isUserInteractionEnabled = false self.noticeNode.isUserInteractionEnabled = false
self.noticeNode.displaysAsynchronously = false self.noticeNode.displaysAsynchronously = false
self.noticeNode.attributedText = NSAttributedString(string: strings.TwoStepAuth_EnterPasswordHelp, font: Font.regular(16.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) self.noticeNode.lineSpacing = 0.1
self.noticeNode.attributedText = NSAttributedString(string: strings.TwoStepAuth_EnterPasswordHelp, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
self.forgotNode = HighlightableButtonNode() self.forgotNode = HighlightableButtonNode()
self.forgotNode.displaysAsynchronously = false self.forgotNode.displaysAsynchronously = false

View File

@ -43,7 +43,8 @@ final class AuthorizationSequencePasswordRecoveryControllerNode: ASDisplayNode,
self.noticeNode = ASTextNode() self.noticeNode = ASTextNode()
self.noticeNode.isUserInteractionEnabled = false self.noticeNode.isUserInteractionEnabled = false
self.noticeNode.displaysAsynchronously = false self.noticeNode.displaysAsynchronously = false
self.noticeNode.attributedText = NSAttributedString(string: strings.TwoStepAuth_RecoveryCodeHelp, font: Font.regular(16.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) self.noticeNode.attributedText = NSAttributedString(string: strings.TwoStepAuth_RecoveryCodeHelp, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
self.noticeNode.lineSpacing = 0.1
self.noAccessNode = HighlightableButtonNode() self.noAccessNode = HighlightableButtonNode()
self.noAccessNode.displaysAsynchronously = false self.noAccessNode.displaysAsynchronously = false

View File

@ -32,12 +32,12 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
var inProgress: Bool = false { var inProgress: Bool = false {
didSet { didSet {
if self.inProgress { // if self.inProgress {
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor)) // let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor))
self.navigationItem.rightBarButtonItem = item // self.navigationItem.rightBarButtonItem = item
} else { // } else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) // self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
} // }
self.controllerNode.inProgress = self.inProgress self.controllerNode.inProgress = self.inProgress
} }
} }

View File

@ -29,25 +29,27 @@ private final class PhoneAndCountryNode: ASDisplayNode {
init(strings: PresentationStrings, theme: PresentationTheme) { init(strings: PresentationStrings, theme: PresentationTheme) {
self.strings = strings self.strings = strings
let countryButtonBackground = generateImage(CGSize(width: 68.0, height: 67.0), rotatedContext: { size, context in let inset: CGFloat = 24.0
let countryButtonBackground = generateImage(CGSize(width: 136.0, height: 67.0), rotatedContext: { size, context in
let arrowSize: CGFloat = 10.0 let arrowSize: CGFloat = 10.0
let lineWidth = UIScreenPixel let lineWidth = UIScreenPixel
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor) context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setLineWidth(lineWidth) context.setLineWidth(lineWidth)
context.move(to: CGPoint(x: 15.0, y: lineWidth / 2.0)) context.move(to: CGPoint(x: inset, y: lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0)) context.addLine(to: CGPoint(x: size.width - inset, y: lineWidth / 2.0))
context.strokePath() context.strokePath()
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize - lineWidth / 2.0)) context.move(to: CGPoint(x: size.width - inset, y: size.height - arrowSize - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize - lineWidth / 2.0)) context.addLine(to: CGPoint(x: 69.0, y: size.height - arrowSize - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height - lineWidth / 2.0)) context.addLine(to: CGPoint(x: 69.0 - arrowSize, y: size.height - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0)) context.addLine(to: CGPoint(x: 69.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0))
context.addLine(to: CGPoint(x: 15.0, y: size.height - arrowSize - lineWidth / 2.0)) context.addLine(to: CGPoint(x: inset, y: size.height - arrowSize - lineWidth / 2.0))
context.strokePath() context.strokePath()
})?.stretchableImage(withLeftCapWidth: 67, topCapHeight: 1) })?.stretchableImage(withLeftCapWidth: 69, topCapHeight: 1)
let countryButtonHighlightedBackground = generateImage(CGSize(width: 68.0, height: 67.0), rotatedContext: { size, context in let countryButtonHighlightedBackground = generateImage(CGSize(width: 70.0, height: 67.0), rotatedContext: { size, context in
let arrowSize: CGFloat = 10.0 let arrowSize: CGFloat = 10.0
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.list.itemHighlightedBackgroundColor.cgColor) context.setFillColor(theme.list.itemHighlightedBackgroundColor.cgColor)
@ -58,18 +60,18 @@ private final class PhoneAndCountryNode: ASDisplayNode {
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize)) context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize))
context.closePath() context.closePath()
context.fillPath() context.fillPath()
})?.stretchableImage(withLeftCapWidth: 67, topCapHeight: 2) })?.stretchableImage(withLeftCapWidth: 69, topCapHeight: 2)
let phoneInputBackground = generateImage(CGSize(width: 96.0, height: 57.0), rotatedContext: { size, context in let phoneInputBackground = generateImage(CGSize(width: 96.0, height: 57.0), rotatedContext: { size, context in
let lineWidth = UIScreenPixel let lineWidth = UIScreenPixel
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor) context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
context.setLineWidth(lineWidth) context.setLineWidth(lineWidth)
context.move(to: CGPoint(x: 15.0, y: size.height - lineWidth / 2.0)) context.move(to: CGPoint(x: inset, y: size.height - lineWidth / 2.0))
context.addLine(to: CGPoint(x: size.width, y: size.height - lineWidth / 2.0)) context.addLine(to: CGPoint(x: size.width, y: size.height - lineWidth / 2.0))
context.strokePath() context.strokePath()
context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - lineWidth / 2.0)) context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - 9.0))
context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 0.0)) context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 8.0))
context.strokePath() context.strokePath()
})?.stretchableImage(withLeftCapWidth: 95, topCapHeight: 2) })?.stretchableImage(withLeftCapWidth: 95, topCapHeight: 2)
@ -107,7 +109,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
self.phoneInputNode.countryCodeField.textField.disableAutomaticKeyboardHandling = [.forward] self.phoneInputNode.countryCodeField.textField.disableAutomaticKeyboardHandling = [.forward]
self.phoneInputNode.numberField.textField.disableAutomaticKeyboardHandling = [.forward] self.phoneInputNode.numberField.textField.disableAutomaticKeyboardHandling = [.forward]
self.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 10.0, right: 0.0) self.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 24.0 + 16.0, bottom: 10.0, right: 0.0)
self.countryButton.contentHorizontalAlignment = .left self.countryButton.contentHorizontalAlignment = .left
self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside) self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside)
@ -159,7 +161,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: [])
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
} else { } else {
strongSelf.countryButton.setTitle(strings.Login_SelectCountry_Title, with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) strongSelf.countryButton.setTitle(strings.Login_SelectCountry, with: Font.regular(20.0), with: theme.list.itemAccentColor, for: [])
strongSelf.phoneInputNode.mask = nil strongSelf.phoneInputNode.mask = nil
strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
} }
@ -188,13 +190,14 @@ private final class PhoneAndCountryNode: ASDisplayNode {
super.layout() super.layout()
let size = self.bounds.size let size = self.bounds.size
let inset: CGFloat = 24.0
self.countryButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 67.0)) self.countryButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 67.0))
self.phoneBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - 57.0), size: CGSize(width: size.width, height: 57.0)) self.phoneBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - 57.0), size: CGSize(width: size.width - inset, height: 57.0))
let countryCodeFrame = CGRect(origin: CGPoint(x: 18.0, y: size.height - 57.0), size: CGSize(width: 71.0, height: 57.0)) let countryCodeFrame = CGRect(origin: CGPoint(x: 18.0, y: size.height - 58.0), size: CGSize(width: 71.0, height: 57.0))
let numberFrame = CGRect(origin: CGPoint(x: 107.0, y: size.height - 57.0), size: CGSize(width: size.width - 96.0 - 8.0, height: 57.0)) let numberFrame = CGRect(origin: CGPoint(x: 107.0, y: size.height - 58.0), size: CGSize(width: size.width - 96.0 - 8.0, height: 57.0))
let placeholderFrame = numberFrame.offsetBy(dx: 0.0, dy: 16.0) let placeholderFrame = numberFrame.offsetBy(dx: 0.0, dy: 17.0 - UIScreenPixel)
let phoneInputFrame = countryCodeFrame.union(numberFrame) let phoneInputFrame = countryCodeFrame.union(numberFrame)
@ -227,10 +230,11 @@ private final class ContactSyncNode: ASDisplayNode {
func updateLayout(width: CGFloat) -> CGSize { func updateLayout(width: CGFloat) -> CGSize {
let switchSize = CGSize(width: 51.0, height: 31.0) let switchSize = CGSize(width: 51.0, height: 31.0)
let titleSize = self.titleNode.updateLayout(CGSize(width: width - switchSize.width - 16.0 * 2.0 - 8.0, height: .greatestFiniteMagnitude)) let inset: CGFloat = 24.0
let titleSize = self.titleNode.updateLayout(CGSize(width: width - switchSize.width - inset * 2.0 - 8.0, height: .greatestFiniteMagnitude))
let height: CGFloat = 40.0 let height: CGFloat = 40.0
self.titleNode.frame = CGRect(origin: CGPoint(x: 16.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) self.titleNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
self.switchNode.frame = CGRect(origin: CGPoint(x: width - 16.0 - switchSize.width, y: floor((height - switchSize.height) / 2.0)), size: switchSize) self.switchNode.frame = CGRect(origin: CGPoint(x: width - inset - switchSize.width, y: floor((height - switchSize.height) / 2.0)), size: switchSize)
return CGSize(width: width, height: height) return CGSize(width: width, height: height)
} }
} }
@ -286,6 +290,14 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.phoneAndCountryNode.phoneInputNode.enableEditing = !self.inProgress self.phoneAndCountryNode.phoneInputNode.enableEditing = !self.inProgress
self.phoneAndCountryNode.phoneInputNode.alpha = self.inProgress ? 0.6 : 1.0 self.phoneAndCountryNode.phoneInputNode.alpha = self.inProgress ? 0.6 : 1.0
self.phoneAndCountryNode.countryButton.isEnabled = !self.inProgress self.phoneAndCountryNode.countryButton.isEnabled = !self.inProgress
if self.inProgress != oldValue {
if self.inProgress {
self.proceedNode.transitionToProgress()
} else {
self.proceedNode.transitionFromProgress()
}
}
} }
} }
@ -299,7 +311,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.hasOtherAccounts = hasOtherAccounts self.hasOtherAccounts = hasOtherAccounts
self.animationNode = DefaultAnimatedStickerNodeImpl() self.animationNode = DefaultAnimatedStickerNodeImpl()
self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroPhone"), width: 256, height: 256, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroPhone"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
self.animationNode.visibility = true self.animationNode.visibility = true
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
@ -311,13 +323,15 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.noticeNode.maximumNumberOfLines = 0 self.noticeNode.maximumNumberOfLines = 0
self.noticeNode.isUserInteractionEnabled = true self.noticeNode.isUserInteractionEnabled = true
self.noticeNode.displaysAsynchronously = false self.noticeNode.displaysAsynchronously = false
self.noticeNode.attributedText = NSAttributedString(string: strings.Login_PhoneAndCountryHelp, font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center) self.noticeNode.lineSpacing = 0.1
self.noticeNode.attributedText = NSAttributedString(string: strings.Login_PhoneAndCountryHelp, font: Font.regular(17.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
self.contactSyncNode = ContactSyncNode(theme: theme, strings: strings) self.contactSyncNode = ContactSyncNode(theme: theme, strings: strings)
self.phoneAndCountryNode = PhoneAndCountryNode(strings: strings, theme: theme) self.phoneAndCountryNode = PhoneAndCountryNode(strings: strings, theme: theme)
self.proceedNode = SolidRoundedButtonNode(title: "Continue", theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) self.proceedNode = SolidRoundedButtonNode(title: "Continue", theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false)
self.proceedNode.progressType = .embedded
super.init() super.init()
@ -372,25 +386,27 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: []) var insets = layout.insets(options: [])
insets.top = navigationBarHeight insets.top = layout.statusBarHeight ?? 20.0
if let inputHeight = layout.inputHeight, !inputHeight.isZero { if let inputHeight = layout.inputHeight, !inputHeight.isZero {
insets.bottom += max(inputHeight, layout.standardInputHeight) insets.bottom = max(inputHeight, insets.bottom)
} }
self.titleNode.attributedText = NSAttributedString(string: strings.Login_PhoneTitle, font: Font.semibold(28.0), textColor: self.theme.list.itemPrimaryTextColor) self.titleNode.attributedText = NSAttributedString(string: strings.Login_PhoneTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor)
let inset: CGFloat = 24.0
let animationSize = CGSize(width: 88.0, height: 88.0) let animationSize = CGSize(width: 88.0, height: 88.0)
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
let noticeSize = self.noticeNode.measure(CGSize(width: min(274.0, layout.size.width - 28.0), height: CGFloat.greatestFiniteMagnitude)) let noticeSize = self.noticeNode.measure(CGSize(width: min(274.0, layout.size.width - 28.0), height: CGFloat.greatestFiniteMagnitude))
let proceedHeight = self.proceedNode.updateLayout(width: layout.size.width - 48.0, transition: transition) let proceedHeight = self.proceedNode.updateLayout(width: layout.size.width - inset * 2.0, transition: transition)
let proceedSize = CGSize(width: layout.size.width - 48.0, height: proceedHeight) let proceedSize = CGSize(width: layout.size.width - inset * 2.0, height: proceedHeight)
var items: [AuthorizationLayoutItem] = [ var items: [AuthorizationLayoutItem] = [
AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 20.0, maxValue: 20.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
AuthorizationLayoutItem(node: self.phoneAndCountryNode, size: CGSize(width: layout.size.width, height: 115.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 44.0, maxValue: 44.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.phoneAndCountryNode, size: CGSize(width: layout.size.width, height: 115.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 30.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
] ]
let contactSyncSize = self.contactSyncNode.updateLayout(width: layout.size.width) let contactSyncSize = self.contactSyncNode.updateLayout(width: layout.size.width)
if self.hasOtherAccounts { if self.hasOtherAccounts {
@ -400,11 +416,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.contactSyncNode.isHidden = true self.contactSyncNode.isHidden = true
} }
items.append(AuthorizationLayoutItem(node: self.proceedNode, size: proceedSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) transition.updateFrame(node: self.proceedNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - proceedSize.width) / 2.0), y: layout.size.height - insets.bottom - proceedSize.height - inset), size: proceedSize))
self.animationNode.updateLayout(size: animationSize) self.animationNode.updateLayout(size: animationSize)
let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 10.0)), items: items, transition: transition, failIfDoesNotFit: false) let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 80.0)), items: items, transition: transition, failIfDoesNotFit: false)
} }
func activateInput() { func activateInput() {

View File

@ -676,7 +676,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
case .scam: case .scam:
titleCredibilityContent = .scam(color: self.theme.chat.message.incoming.scamColor) titleCredibilityContent = .scam(color: self.theme.chat.message.incoming.scamColor)
case let .emojiStatus(emojiStatus): case let .emojiStatus(emojiStatus):
titleCredibilityContent = .emojiStatus(status: emojiStatus, placeholderColor: self.theme.list.mediaPlaceholderColor) titleCredibilityContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor)
} }
let titleCredibilitySize = self.titleCredibilityIconView.update( let titleCredibilitySize = self.titleCredibilityIconView.update(

View File

@ -2023,7 +2023,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)? var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)?
var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)? var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)?
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Bool) -> Void)? var displayPremiumIntro: ((UIView, PeerEmojiStatus?, TelegramMediaFile?, String?, Bool) -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition? var navigationTransition: PeerInfoHeaderNavigationTransition?
@ -2033,6 +2033,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let animationCache: AnimationCache let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer let animationRenderer: MultiAnimationRenderer
var emojiStatusPackDisposable = MetaDisposable()
var emojiStatusFile: TelegramMediaFile?
var emojiStatusPackTitle: String?
init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool) { init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool) {
self.context = context self.context = context
self.isAvatarExpanded = avatarInitiallyExpanded self.isAvatarExpanded = avatarInitiallyExpanded
@ -2185,6 +2189,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
} }
deinit {
self.emojiStatusPackDisposable.dispose()
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
@ -2214,7 +2222,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
func invokeDisplayPremiumIntro() { func invokeDisplayPremiumIntro() {
self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, self.isAvatarExpanded) self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, nil, nil, self.isAvatarExpanded)
} }
func initiateAvatarExpansion(gallery: Bool, first: Bool) { func initiateAvatarExpansion(gallery: Bool, first: Bool) {
@ -2424,8 +2432,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
emojiExpandedStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor) emojiExpandedStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor)
case let .emojiStatus(emojiStatus): case let .emojiStatus(emojiStatus):
currentEmojiStatus = emojiStatus currentEmojiStatus = emojiStatus
emojiRegularStatusContent = .emojiStatus(status: emojiStatus, placeholderColor: presentationData.theme.list.mediaPlaceholderColor) emojiRegularStatusContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor)
emojiExpandedStatusContent = .emojiStatus(status: emojiStatus, placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.15)) emojiExpandedStatusContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.15))
} }
let iconSize = self.titleCredibilityIconView.update( let iconSize = self.titleCredibilityIconView.update(
@ -2439,13 +2447,50 @@ final class PeerInfoHeaderNode: ASDisplayNode {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, currentEmojiStatus, false) strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFile, strongSelf.emojiStatusPackTitle, false)
}, },
longTapAction: { [weak self] in longTapAction: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let _ = strongSelf.context.engine.accountData.setEmojiStatus(file: nil).start() let _ = strongSelf.context.engine.accountData.setEmojiStatus(file: nil).start()
}, emojiFileUpdated: { [weak self] emojiFile in
guard let strongSelf = self else {
return
}
if let emojiFile = emojiFile {
strongSelf.emojiStatusFile = nil
for attribute in emojiFile.attributes {
if case let .CustomEmoji(_, _, packReference) = attribute, let packReference = packReference {
strongSelf.emojiStatusPackDisposable.set((strongSelf.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false)
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> mapToSignal { result -> Signal<(TelegramMediaFile, String)?, NoError> in
if case let .result(info, items, _) = result {
return .single(items.first.flatMap { ($0.file, info.title) })
} else {
return .complete()
}
}).start(next: { fileAndPackTitle in
guard let strongSelf = self else {
return
}
strongSelf.emojiStatusFile = fileAndPackTitle?.0
strongSelf.emojiStatusPackTitle = fileAndPackTitle?.1
}))
break
}
}
} else {
strongSelf.emojiStatusFile = nil
strongSelf.emojiStatusPackDisposable.set(nil)
}
} }
)), )),
environment: {}, environment: {},
@ -2462,7 +2507,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, true) strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFile, strongSelf.emojiStatusPackTitle, true)
}, },
longTapAction: { [weak self] in longTapAction: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -3051,7 +3051,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})) }))
} }
self.headerNode.displayPremiumIntro = { [weak self] sourceView, _, _ in self.headerNode.displayPremiumIntro = { [weak self] sourceView, _, _, _, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -3080,26 +3080,36 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} else { } else {
screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext) screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext)
self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, white in self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFile, emojiPackTitle, white in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let source: PremiumSource let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
if let peerStatus = peerStatus { guard !premiumConfiguration.isPremiumDisabled else {
source = .emojiStatus(strongSelf.peerId, peerStatus.fileId) return
} else {
source = .profile(strongSelf.peerId)
} }
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId))
if !premiumConfiguration.isPremiumDisabled { |> deliverOnMainQueue).start(next: { [weak self] _ in
let controller = PremiumIntroScreen(context: strongSelf.context, source: source) guard let strongSelf = self else {
controller.sourceView = sourceView return
controller.containerView = strongSelf.controller?.navigationController?.view }
controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor if let emojiFile = emojiStatusFile {
strongSelf.controller?.push(controller) let source: PremiumSource
} if let peerStatus = peerStatus {
source = .emojiStatus(strongSelf.peerId, peerStatus.fileId, emojiStatusFile, emojiPackTitle)
} else {
source = .profile(strongSelf.peerId)
}
let controller = PremiumIntroScreen(context: strongSelf.context, source: source)
controller.sourceView = sourceView
controller.containerView = strongSelf.controller?.navigationController?.view
controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor
strongSelf.controller?.push(controller)
}
})
} }
self.headerNode.displayAvatarContextMenu = { [weak self] node, gesture in self.headerNode.displayAvatarContextMenu = { [weak self] node, gesture in

View File

@ -345,7 +345,6 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
let dustNode = InvisibleInkDustNode(textNode: nil) let dustNode = InvisibleInkDustNode(textNode: nil)
self.dustNode = dustNode self.dustNode = dustNode
self.textNode.supernode?.insertSubnode(dustNode, aboveSubnode: self.textNode) self.textNode.supernode?.insertSubnode(dustNode, aboveSubnode: self.textNode)
} }
if let dustNode = self.dustNode { if let dustNode = self.dustNode {
dustNode.update(size: textFrame.size, color: self.theme.chat.inputPanel.secondaryTextColor, textColor: self.theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) dustNode.update(size: textFrame.size, color: self.theme.chat.inputPanel.secondaryTextColor, textColor: self.theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })

View File

@ -1501,8 +1501,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .deeplink(reference) mappedSource = .deeplink(reference)
case let .profile(peerId): case let .profile(peerId):
mappedSource = .profile(peerId) mappedSource = .profile(peerId)
case let .emojiStatus(peerId, fileId): case let .emojiStatus(peerId, fileId, file, packTitle):
mappedSource = .emojiStatus(peerId, fileId) mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
} }
return PremiumIntroScreen(context: context, source: mappedSource) return PremiumIntroScreen(context: context, source: mappedSource)
} }