mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Various improvements
This commit is contained in:
parent
f7a9728ad6
commit
e5a4f5f107
Binary file not shown.
@ -7973,5 +7973,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"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**:";
|
||||
|
||||
"Login.SelectCountry" = "Country";
|
||||
|
||||
@ -780,7 +780,7 @@ public enum PremiumIntroSource {
|
||||
case about
|
||||
case deeplink(String?)
|
||||
case profile(PeerId)
|
||||
case emojiStatus(PeerId, Int64)
|
||||
case emojiStatus(PeerId, Int64, TelegramMediaFile?, String?)
|
||||
}
|
||||
|
||||
#if ENABLE_WALLET
|
||||
|
||||
@ -7,26 +7,32 @@ import TextFormat
|
||||
import Markdown
|
||||
|
||||
public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, strings: PresentationStrings, primaryColor: UIColor, accentColor: UIColor) -> NSAttributedString {
|
||||
let fontSize: CGFloat = 17.0
|
||||
switch type {
|
||||
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:
|
||||
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor)
|
||||
let body = MarkdownAttributeSet(font: Font.regular(fontSize), 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)
|
||||
case .missedCall:
|
||||
let body = MarkdownAttributeSet(font: Font.regular(16.0), textColor: primaryColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(16.0), textColor: primaryColor)
|
||||
let body = MarkdownAttributeSet(font: Font.regular(fontSize), 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)
|
||||
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:
|
||||
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:
|
||||
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, _, _, _, _):
|
||||
//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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1524,7 +1524,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let peer = messages.last?.author {
|
||||
if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
|
||||
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 {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
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 {
|
||||
if case let .user(user) = peer, let emojiStatus = user.emojiStatus {
|
||||
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 {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
|
||||
@ -48,28 +48,38 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
|
||||
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) {
|
||||
if self.borderColorValue != borderColor {
|
||||
self.borderColorValue = 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.borderWidth = 1.0
|
||||
self.backgroundView.layer.borderWidth = 1.0 + UIScreenPixel
|
||||
if let previousColor = previousColor {
|
||||
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
|
||||
self.text = text
|
||||
|
||||
if animated && previousText.isEmpty != text.isEmpty {
|
||||
if !text.isEmpty {
|
||||
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
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.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
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 {
|
||||
if let copyView = self.textNode.view.snapshotContentTree() {
|
||||
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, *) {
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: UIFont.monospacedSystemFont(ofSize: fontSize, weight: .regular), textColor: UIColor(argb: textColor))
|
||||
} else {
|
||||
@ -105,6 +113,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
|
||||
|
||||
private var theme: Theme?
|
||||
private var count: Int?
|
||||
private var prefix: String = ""
|
||||
|
||||
private var textValue: String = ""
|
||||
public var text: String {
|
||||
@ -215,6 +224,15 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
|
||||
let itemView = self.itemViews[i]
|
||||
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)
|
||||
let itemText: String
|
||||
if i < self.textValue.count {
|
||||
@ -222,13 +240,14 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
|
||||
} else {
|
||||
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 {
|
||||
self.theme = theme
|
||||
self.count = count
|
||||
self.prefix = prefix
|
||||
|
||||
if theme.isDark {
|
||||
self.textField.keyboardAppearance = .dark
|
||||
@ -236,11 +255,14 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
|
||||
self.textField.keyboardAppearance = .light
|
||||
}
|
||||
|
||||
let fontSize: CGFloat
|
||||
let height: CGFloat
|
||||
if prefix.isEmpty {
|
||||
height = 40.0
|
||||
height = 51.0
|
||||
fontSize = floor(13.0 * height / 28.0)
|
||||
} else {
|
||||
height = 28.0
|
||||
fontSize = floor(21.0 * height / 28.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 prefixSpacing: CGFloat = prefix.isEmpty ? 0.0 : 8.0
|
||||
|
||||
let itemSize = CGSize(width: floor(25.0 * height / 28.0), height: height)
|
||||
let itemSpacing: CGFloat = 5.0
|
||||
let itemSize = CGSize(width: floor(24.0 * height / 28.0), height: height)
|
||||
let itemSpacing: CGFloat = prefix.isEmpty ? 15.0 : 5.0
|
||||
let itemsWidth: CGFloat = itemSize.width * CGFloat(count) + itemSpacing * CGFloat(count - 1)
|
||||
|
||||
let contentWidth: CGFloat = prefixSize.width + prefixSpacing + itemsWidth
|
||||
@ -276,7 +298,7 @@ public final class CodeInputView: ASDisplayNode, UITextFieldDelegate {
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
if self.itemViews.count > count {
|
||||
|
||||
@ -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",
|
||||
],
|
||||
)
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -155,6 +155,10 @@ public extension ContainerViewLayout {
|
||||
return self.size.width > self.size.height ? .landscape : .portrait
|
||||
}
|
||||
|
||||
var standardKeyboardHeight: CGFloat {
|
||||
return self.deviceMetrics.keyboardHeight(inLandscape: self.orientation == .landscape)
|
||||
}
|
||||
|
||||
var standardInputHeight: CGFloat {
|
||||
return self.deviceMetrics.keyboardHeight(inLandscape: self.orientation == .landscape) + self.deviceMetrics.predictiveInputHeight(inLandscape: self.orientation == .landscape)
|
||||
}
|
||||
|
||||
@ -165,19 +165,11 @@ public struct Font {
|
||||
}
|
||||
|
||||
public static func medium(_ size: CGFloat) -> UIFont {
|
||||
if #available(iOS 8.2, *) {
|
||||
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.medium)
|
||||
} else {
|
||||
return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil)
|
||||
}
|
||||
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.medium)
|
||||
}
|
||||
|
||||
public static func semibold(_ size: CGFloat) -> UIFont {
|
||||
if #available(iOS 8.2, *) {
|
||||
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.semibold)
|
||||
} else {
|
||||
return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil)
|
||||
}
|
||||
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.semibold)
|
||||
}
|
||||
|
||||
public static func bold(_ size: CGFloat) -> UIFont {
|
||||
@ -188,17 +180,12 @@ public struct Font {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static func heavy(_ size: CGFloat) -> UIFont {
|
||||
return self.with(size: size, design: .regular, weight: .heavy, traits: [])
|
||||
}
|
||||
|
||||
public static func light(_ size: CGFloat) -> UIFont {
|
||||
if #available(iOS 8.2, *) {
|
||||
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.light)
|
||||
} else {
|
||||
return CTFontCreateWithName("HelveticaNeue-Light" as CFString, size, nil)
|
||||
}
|
||||
return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.light)
|
||||
}
|
||||
|
||||
public static func semiboldItalic(_ size: CGFloat) -> UIFont {
|
||||
|
||||
@ -8,20 +8,12 @@ import GradientBackground
|
||||
|
||||
private let regularTitleFont = Font.regular(36.0)
|
||||
private let regularSubtitleFont: UIFont = {
|
||||
if #available(iOS 8.2, *) {
|
||||
return UIFont.systemFont(ofSize: 10.0, weight: UIFont.Weight.bold)
|
||||
} else {
|
||||
return CTFontCreateWithName("HelveticaNeue-Bold" as CFString, 10.0, nil)
|
||||
}
|
||||
return UIFont.systemFont(ofSize: 10.0, weight: UIFont.Weight.bold)
|
||||
}()
|
||||
|
||||
private let largeTitleFont = Font.regular(40.0)
|
||||
private let largeSubtitleFont: UIFont = {
|
||||
if #available(iOS 8.2, *) {
|
||||
return UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.bold)
|
||||
} else {
|
||||
return CTFontCreateWithName("HelveticaNeue-Bold" as CFString, 12.0, nil)
|
||||
}
|
||||
return UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.bold)
|
||||
}()
|
||||
|
||||
private func generateButtonImage(background: PasscodeBackground, frame: CGRect, title: String, subtitle: String, highlighted: Bool) -> UIImage? {
|
||||
|
||||
@ -177,7 +177,7 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private func updatePlaceholder() {
|
||||
if let mask = self.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 {
|
||||
mutableMask.replaceCharacters(in: NSRange(location: 0, length: min(text.count, mask.string.count)), with: text)
|
||||
}
|
||||
|
||||
@ -76,12 +76,6 @@ swift_library(
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//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/ConfettiEffect:ConfettiEffect",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
@ -93,6 +87,14 @@ swift_library(
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//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:public",
|
||||
|
||||
322
submodules/PremiumUI/Sources/EmojiHeaderComponent.swift
Normal file
322
submodules/PremiumUI/Sources/EmojiHeaderComponent.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -13,41 +13,6 @@ import TelegramCore
|
||||
|
||||
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 {
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer?
|
||||
@ -174,8 +139,8 @@ class GiftAvatarComponent: Component {
|
||||
private func setupScaleAnimation() {
|
||||
let animation = CABasicAnimation(keyPath: "transform.scale")
|
||||
animation.duration = 2.0
|
||||
animation.fromValue = 1.0 //NSValue(scnVector3: SCNVector3(x: 0.1, y: 0.1, z: 0.1))
|
||||
animation.toValue = 1.15 //NSValue(scnVector3: SCNVector3(x: 0.115, y: 0.115, z: 0.115))
|
||||
animation.fromValue = 1.0
|
||||
animation.toValue = 1.15
|
||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
animation.autoreverses = true
|
||||
animation.repeatCount = .infinity
|
||||
|
||||
@ -11,6 +11,7 @@ import ViewControllerComponent
|
||||
import AccountContext
|
||||
import SolidRoundedButtonComponent
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import BundleIconComponent
|
||||
import SolidRoundedButtonComponent
|
||||
import Markdown
|
||||
@ -20,6 +21,8 @@ import TextFormat
|
||||
import InstantPageCache
|
||||
import UniversalMediaPlayer
|
||||
import CheckNode
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
public enum PremiumSource: Equatable {
|
||||
case settings
|
||||
@ -40,7 +43,7 @@ public enum PremiumSource: Equatable {
|
||||
case animatedEmoji
|
||||
case deeplink(String?)
|
||||
case profile(PeerId)
|
||||
case emojiStatus(PeerId, Int64)
|
||||
case emojiStatus(PeerId, Int64, TelegramMediaFile?, String?)
|
||||
case gift(from: PeerId, to: PeerId, duration: Int32)
|
||||
case giftTerms
|
||||
|
||||
@ -158,7 +161,7 @@ enum PremiumPerk: CaseIterable {
|
||||
case .animatedUserpics:
|
||||
return "animated_userpics"
|
||||
case .appIcons:
|
||||
return "app_icon"
|
||||
return "app_icons"
|
||||
case .animatedEmoji:
|
||||
return "animated_emoji"
|
||||
}
|
||||
@ -1121,6 +1124,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0
|
||||
|
||||
let textColor = theme.list.itemPrimaryTextColor
|
||||
let accentColor = theme.list.itemAccentColor
|
||||
let titleColor = theme.list.itemPrimaryTextColor
|
||||
let subtitleColor = theme.list.itemSecondaryTextColor
|
||||
let arrowColor = theme.list.disclosureArrowColor
|
||||
@ -1130,7 +1134,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
|
||||
let textString: String
|
||||
if case .emojiStatus = context.component.source {
|
||||
textString = strings.Premium_EmojiStatusText
|
||||
textString = strings.Premium_EmojiStatusText.replacingOccurrences(of: "#", with: "# ")
|
||||
} else if case .giftTerms = context.component.source {
|
||||
textString = strings.Premium_PersonalDescription
|
||||
} else if let _ = context.component.otherPeerName {
|
||||
@ -1149,9 +1153,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
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
|
||||
})
|
||||
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(
|
||||
@ -1485,9 +1490,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { attributes, _ in
|
||||
tapAction: { [weak environment] attributes, _ in
|
||||
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") {
|
||||
controller.context.sharedContext.applicationBindings.openSubscriptions()
|
||||
} else if url.hasPrefix("https://") || url.hasPrefix("tg://") {
|
||||
@ -1636,6 +1641,14 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
var isPremium: Bool?
|
||||
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 paymentDisposable = MetaDisposable()
|
||||
private var activationDisposable = MetaDisposable()
|
||||
@ -1654,6 +1667,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
self.present = present
|
||||
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()
|
||||
|
||||
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
|
||||
@ -1676,7 +1694,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
|> map { peer -> String? in
|
||||
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))
|
||||
|> map { peer -> String? in
|
||||
return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
@ -1704,12 +1722,30 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
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 {
|
||||
self.disposable?.dispose()
|
||||
self.paymentDisposable.dispose()
|
||||
self.activationDisposable.dispose()
|
||||
self.emojiFileDisposable?.dispose()
|
||||
}
|
||||
|
||||
func buy() {
|
||||
@ -1830,10 +1866,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
let background = Child(Rectangle.self)
|
||||
let scrollContent = Child(ScrollComponent<EnvironmentType>.self)
|
||||
let star = Child(PremiumStarComponent.self)
|
||||
let emoji = Child(EmojiHeaderComponent.self)
|
||||
let topPanel = Child(BlurredRectangle.self)
|
||||
let topSeparator = Child(Rectangle.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let secondaryTitle = Child(MultilineTextComponent.self)
|
||||
let secondaryTitle = Child(MultilineTextWithEntitiesComponent.self)
|
||||
let bottomPanel = Child(BlurredRectangle.self)
|
||||
let bottomSeparator = Child(Rectangle.self)
|
||||
let button = Child(SolidRoundedButtonComponent.self)
|
||||
@ -1853,12 +1890,33 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
if case .profile = context.component.source {
|
||||
isIntro = false
|
||||
}
|
||||
|
||||
let star = 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 header: _UpdatedChildComponent
|
||||
if case let .emojiStatus(_, fileId, _, _) = context.component.source {
|
||||
header = emoji.update(
|
||||
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(
|
||||
component: BlurredRectangle(
|
||||
@ -1899,7 +1957,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
)
|
||||
|
||||
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 boldTextFont = Font.bold(18.0)
|
||||
@ -1908,10 +1971,12 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
return nil
|
||||
})
|
||||
|
||||
var highlightableLinks = false
|
||||
let secondaryTitleText: String
|
||||
if let otherPeerName = state.otherPeerName {
|
||||
if case .emojiStatus = context.component.source {
|
||||
secondaryTitleText = environment.strings.Premium_EmojiStatusTitle(otherPeerName, "").string
|
||||
if case let .emojiStatus(_, _, _, emojiPackTitle) = context.component.source {
|
||||
highlightableLinks = true
|
||||
secondaryTitleText = environment.strings.Premium_EmojiStatusTitle(otherPeerName, emojiPackTitle ?? "").string.replacingOccurrences(of: "#", with: " # ")
|
||||
} else if case .profile = context.component.source {
|
||||
secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string
|
||||
} else if case let .gift(fromPeerId, _, duration) = context.component.source {
|
||||
@ -1943,13 +2008,47 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
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(
|
||||
component: MultilineTextComponent(
|
||||
text: .markdown(text: secondaryTitleText, attributes: markdownAttributes),
|
||||
component: MultilineTextWithEntitiesComponent(
|
||||
context: context.component.context,
|
||||
animationCache: context.state.animationCache,
|
||||
animationRenderer: context.state.animationRenderer,
|
||||
placeholderColor: environment.theme.list.mediaPlaceholderColor,
|
||||
text: .plain(secondaryAttributedText),
|
||||
horizontalAlignment: .center,
|
||||
truncationType: .end,
|
||||
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),
|
||||
transition: context.transition
|
||||
@ -2035,8 +2134,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
titleAlpha = state.otherPeerName != nil ? 0.0 : 1.0
|
||||
}
|
||||
|
||||
context.add(star
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + star.size.height / 2.0 - 30.0 - titleOffset * titleScale))
|
||||
context.add(header
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + header.size.height / 2.0 - 30.0 - titleOffset * titleScale))
|
||||
.scale(titleScale)
|
||||
)
|
||||
|
||||
@ -2251,6 +2350,19 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
self.didSetReady = true
|
||||
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 {
|
||||
view.animateFrom = sourceView
|
||||
view.containerView = self.containerView
|
||||
|
||||
@ -162,6 +162,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) {
|
||||
self.theme = theme
|
||||
@ -570,43 +572,91 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
|
||||
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 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()
|
||||
progressNode.displaysAsynchronously = false
|
||||
progressNode.frame = progressFrame
|
||||
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
|
||||
self.insertSubnode(progressNode, at: 0)
|
||||
|
||||
switch self.progressType {
|
||||
case .fullSize:
|
||||
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
|
||||
|
||||
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.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
||||
|
||||
self.subtitleNode.alpha = 0.0
|
||||
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 {
|
||||
|
||||
@ -21,7 +21,7 @@ public final class EmojiStatusComponent: Component {
|
||||
case verified(fillColor: UIColor, foregroundColor: UIColor)
|
||||
case fake(color: UIColor)
|
||||
case scam(color: UIColor)
|
||||
case emojiStatus(status: PeerEmojiStatus, placeholderColor: UIColor)
|
||||
case emojiStatus(status: PeerEmojiStatus, size: CGSize, placeholderColor: UIColor)
|
||||
}
|
||||
|
||||
public let context: AccountContext
|
||||
@ -30,6 +30,7 @@ public final class EmojiStatusComponent: Component {
|
||||
public let content: Content
|
||||
public let action: (() -> Void)?
|
||||
public let longTapAction: (() -> Void)?
|
||||
public let emojiFileUpdated: ((TelegramMediaFile?) -> Void)?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -37,7 +38,8 @@ public final class EmojiStatusComponent: Component {
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
content: Content,
|
||||
action: (() -> Void)?,
|
||||
longTapAction: (() -> Void)?
|
||||
longTapAction: (() -> Void)?,
|
||||
emojiFileUpdated: ((TelegramMediaFile?) -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.animationCache = animationCache
|
||||
@ -45,6 +47,7 @@ public final class EmojiStatusComponent: Component {
|
||||
self.content = content
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
self.emojiFileUpdated = emojiFileUpdated
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiStatusComponent, rhs: EmojiStatusComponent) -> Bool {
|
||||
@ -105,6 +108,7 @@ public final class EmojiStatusComponent: Component {
|
||||
var iconImage: UIImage?
|
||||
var emojiFileId: Int64?
|
||||
var emojiPlaceholderColor: UIColor?
|
||||
var emojiSize = CGSize()
|
||||
|
||||
/*
|
||||
if case .fake = credibilityIcon {
|
||||
@ -213,12 +217,13 @@ public final class EmojiStatusComponent: Component {
|
||||
iconImage = nil
|
||||
case .scam:
|
||||
iconImage = nil
|
||||
case let .emojiStatus(emojiStatus, placeholderColor):
|
||||
case let .emojiStatus(emojiStatus, size, placeholderColor):
|
||||
iconImage = nil
|
||||
emojiFileId = emojiStatus.fileId
|
||||
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 {
|
||||
self.emojiFileDisposable?.dispose()
|
||||
self.emojiFileDisposable = nil
|
||||
@ -237,9 +242,10 @@ public final class EmojiStatusComponent: Component {
|
||||
}
|
||||
} else {
|
||||
iconImage = self.iconView?.image
|
||||
if case let .emojiStatus(status, placeholderColor) = component.content {
|
||||
if case let .emojiStatus(status, size, placeholderColor) = component.content {
|
||||
emojiFileId = status.fileId
|
||||
emojiPlaceholderColor = placeholderColor
|
||||
emojiSize = size
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,6 +279,7 @@ public final class EmojiStatusComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
let emojiFileUpdated = component.emojiFileUpdated
|
||||
if let emojiFileId = emojiFileId, let emojiPlaceholderColor = emojiPlaceholderColor {
|
||||
size = availableSize
|
||||
|
||||
@ -292,7 +299,7 @@ public final class EmojiStatusComponent: Component {
|
||||
cache: component.animationCache,
|
||||
renderer: component.animationRenderer,
|
||||
placeholderColor: emojiPlaceholderColor,
|
||||
pointSize: CGSize(width: 32.0, height: 32.0)
|
||||
pointSize: emojiSize
|
||||
)
|
||||
self.animationLayer = animationLayer
|
||||
self.layer.addSublayer(animationLayer)
|
||||
@ -311,10 +318,17 @@ public final class EmojiStatusComponent: Component {
|
||||
}
|
||||
strongSelf.emojiFile = result[emojiFileId]
|
||||
strongSelf.state?.updated(transition: .immediate)
|
||||
|
||||
emojiFileUpdated?(result[emojiFileId])
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let _ = self.emojiFile {
|
||||
self.emojiFile = nil
|
||||
emojiFileUpdated?(nil)
|
||||
}
|
||||
|
||||
self.emojiFileDisposable?.dispose()
|
||||
self.emojiFileDisposable = nil
|
||||
|
||||
|
||||
@ -30,12 +30,12 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
if self.inProgress {
|
||||
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor))
|
||||
self.navigationItem.rightBarButtonItem = item
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
// if self.inProgress {
|
||||
// let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor))
|
||||
// self.navigationItem.rightBarButtonItem = item
|
||||
// } else {
|
||||
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
// }
|
||||
self.controllerNode.inProgress = self.inProgress
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import PhoneNumberFormat
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import SolidRoundedButtonNode
|
||||
import InvisibleInkDustNode
|
||||
|
||||
final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let strings: PresentationStrings
|
||||
@ -21,7 +22,9 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let titleIconNode: ASImageNode
|
||||
private let currentOptionNode: ASTextNode
|
||||
private let currentOptionNode: ImmediateTextNode
|
||||
private var dustNode: InvisibleInkDustNode?
|
||||
|
||||
private let currentOptionInfoNode: ASTextNode
|
||||
private let nextOptionTitleNode: ImmediateTextNode
|
||||
private let nextOptionButtonNode: HighlightableButtonNode
|
||||
@ -84,9 +87,11 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.titleIconNode.displayWithoutProcessing = true
|
||||
self.titleIconNode.displaysAsynchronously = false
|
||||
|
||||
self.currentOptionNode = ASTextNode()
|
||||
self.currentOptionNode = ImmediateTextNode()
|
||||
self.currentOptionNode.isUserInteractionEnabled = false
|
||||
self.currentOptionNode.displaysAsynchronously = false
|
||||
self.currentOptionNode.lineSpacing = 0.1
|
||||
self.currentOptionNode.maximumNumberOfLines = 0
|
||||
|
||||
self.currentOptionInfoNode = ASTextNode()
|
||||
self.currentOptionInfoNode.isUserInteractionEnabled = false
|
||||
@ -122,23 +127,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
(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()
|
||||
|
||||
self.setViewBlock({
|
||||
@ -165,11 +153,6 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
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.signInWithAppleButton?.addTarget(self, action: #selector(self.signInWithApplePressed), for: .touchUpInside)
|
||||
}
|
||||
@ -189,8 +172,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
func updateCode(_ code: String) {
|
||||
self.codeInputView.text = code
|
||||
self.textChanged(text: code)
|
||||
//self.codeField.textField.text = code
|
||||
//self.codeFieldTextChanged(self.codeField.textField)
|
||||
|
||||
if let codeType = self.codeType {
|
||||
var codeLength: Int32?
|
||||
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)
|
||||
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 {
|
||||
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 DEBUG
|
||||
@ -272,13 +254,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
self.layoutArguments = (layout, navigationBarHeight)
|
||||
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top = navigationBarHeight
|
||||
|
||||
if let inputHeight = layout.inputHeight {
|
||||
insets.bottom += max(inputHeight, layout.standardInputHeight)
|
||||
}
|
||||
|
||||
|
||||
insets.top = layout.statusBarHeight ?? 20.0
|
||||
|
||||
var animationName = "IntroMessage"
|
||||
if let codeType = self.codeType {
|
||||
switch 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
let animationSize = CGSize(width: 88.0, height: 88.0)
|
||||
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 nextOptionSize = self.nextOptionTitleNode.updateLayout(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
@ -347,9 +332,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
var additionalBottomInset: CGFloat = 20.0
|
||||
if let codeType = self.codeType {
|
||||
switch codeType {
|
||||
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)))
|
||||
default:
|
||||
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.currentOptionNode, size: currentOptionSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.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: 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.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 {
|
||||
additionalBottomInset = 80.0
|
||||
|
||||
self.nextOptionButtonNode.isHidden = true
|
||||
signInWithAppleButton.isHidden = false
|
||||
|
||||
let inset: CGFloat = 24.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 dividerSize = self.dividerNode.updateLayout(width: layout.size.width)
|
||||
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 - 48.0, height: 50.0)
|
||||
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 {
|
||||
self.signInWithAppleButton?.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.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)))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
|
||||
self.theme = theme
|
||||
|
||||
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.titleNode = ASTextNode()
|
||||
@ -95,6 +95,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
|
||||
self.noticeNode = ASTextNode()
|
||||
self.noticeNode.isUserInteractionEnabled = 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)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
@ -186,13 +187,13 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText
|
||||
self.layoutArguments = (layout, navigationBarHeight)
|
||||
|
||||
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)
|
||||
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 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)
|
||||
|
||||
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)
|
||||
|
||||
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.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)))
|
||||
|
||||
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 {
|
||||
signInWithAppleButton.isHidden = false
|
||||
|
||||
@ -50,7 +50,8 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT
|
||||
self.noticeNode = ASTextNode()
|
||||
self.noticeNode.isUserInteractionEnabled = 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.displaysAsynchronously = false
|
||||
|
||||
@ -43,7 +43,8 @@ final class AuthorizationSequencePasswordRecoveryControllerNode: ASDisplayNode,
|
||||
self.noticeNode = ASTextNode()
|
||||
self.noticeNode.isUserInteractionEnabled = 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.displaysAsynchronously = false
|
||||
|
||||
@ -32,12 +32,12 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
if self.inProgress {
|
||||
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor))
|
||||
self.navigationItem.rightBarButtonItem = item
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
// if self.inProgress {
|
||||
// let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor))
|
||||
// self.navigationItem.rightBarButtonItem = item
|
||||
// } else {
|
||||
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed))
|
||||
// }
|
||||
self.controllerNode.inProgress = self.inProgress
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,25 +29,27 @@ private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
init(strings: PresentationStrings, theme: PresentationTheme) {
|
||||
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 lineWidth = UIScreenPixel
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.move(to: CGPoint(x: 15.0, y: lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0))
|
||||
context.move(to: CGPoint(x: inset, y: lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - inset, y: lineWidth / 2.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: size.width, 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: size.width - 1.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: 15.0, 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: 69.0, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: 69.0 - arrowSize, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: 69.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: inset, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
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
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
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.closePath()
|
||||
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 lineWidth = UIScreenPixel
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(theme.list.itemPlainSeparatorColor.cgColor)
|
||||
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.strokePath()
|
||||
context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 0.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: 8.0))
|
||||
context.strokePath()
|
||||
})?.stretchableImage(withLeftCapWidth: 95, topCapHeight: 2)
|
||||
|
||||
@ -107,7 +109,7 @@ private final class PhoneAndCountryNode: ASDisplayNode {
|
||||
self.phoneInputNode.countryCodeField.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.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.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor)
|
||||
} 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.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()
|
||||
|
||||
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.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 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 placeholderFrame = numberFrame.offsetBy(dx: 0.0, dy: 16.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 - 58.0), size: CGSize(width: size.width - 96.0 - 8.0, height: 57.0))
|
||||
let placeholderFrame = numberFrame.offsetBy(dx: 0.0, dy: 17.0 - UIScreenPixel)
|
||||
|
||||
let phoneInputFrame = countryCodeFrame.union(numberFrame)
|
||||
|
||||
@ -227,10 +230,11 @@ private final class ContactSyncNode: ASDisplayNode {
|
||||
|
||||
func updateLayout(width: CGFloat) -> CGSize {
|
||||
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
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 16.0, 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.titleNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -286,6 +290,14 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.phoneAndCountryNode.phoneInputNode.enableEditing = !self.inProgress
|
||||
self.phoneAndCountryNode.phoneInputNode.alpha = self.inProgress ? 0.6 : 1.0
|
||||
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.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.titleNode = ASTextNode()
|
||||
@ -311,13 +323,15 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
self.noticeNode.maximumNumberOfLines = 0
|
||||
self.noticeNode.isUserInteractionEnabled = true
|
||||
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.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.progressType = .embedded
|
||||
|
||||
super.init()
|
||||
|
||||
@ -372,25 +386,27 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var insets = layout.insets(options: [])
|
||||
insets.top = navigationBarHeight
|
||||
insets.top = layout.statusBarHeight ?? 20.0
|
||||
|
||||
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 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 proceedHeight = self.proceedNode.updateLayout(width: layout.size.width - 48.0, transition: transition)
|
||||
let proceedSize = CGSize(width: layout.size.width - 48.0, height: proceedHeight)
|
||||
let proceedHeight = self.proceedNode.updateLayout(width: layout.size.width - inset * 2.0, transition: transition)
|
||||
let proceedSize = CGSize(width: layout.size.width - inset * 2.0, height: proceedHeight)
|
||||
|
||||
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.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 20.0, maxValue: 20.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.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: 30.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)),
|
||||
]
|
||||
let contactSyncSize = self.contactSyncNode.updateLayout(width: layout.size.width)
|
||||
if self.hasOtherAccounts {
|
||||
@ -400,11 +416,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
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)
|
||||
|
||||
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() {
|
||||
|
||||
@ -676,7 +676,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
case .scam:
|
||||
titleCredibilityContent = .scam(color: self.theme.chat.message.incoming.scamColor)
|
||||
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(
|
||||
|
||||
@ -2023,7 +2023,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)?
|
||||
|
||||
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Bool) -> Void)?
|
||||
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, TelegramMediaFile?, String?, Bool) -> Void)?
|
||||
|
||||
var navigationTransition: PeerInfoHeaderNavigationTransition?
|
||||
|
||||
@ -2033,6 +2033,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
var emojiStatusPackDisposable = MetaDisposable()
|
||||
var emojiStatusFile: TelegramMediaFile?
|
||||
var emojiStatusPackTitle: String?
|
||||
|
||||
init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool) {
|
||||
self.context = context
|
||||
self.isAvatarExpanded = avatarInitiallyExpanded
|
||||
@ -2185,6 +2189,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiStatusPackDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
@ -2214,7 +2222,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -2424,8 +2432,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
emojiExpandedStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor)
|
||||
case let .emojiStatus(emojiStatus):
|
||||
currentEmojiStatus = emojiStatus
|
||||
emojiRegularStatusContent = .emojiStatus(status: emojiStatus, placeholderColor: presentationData.theme.list.mediaPlaceholderColor)
|
||||
emojiExpandedStatusContent = .emojiStatus(status: emojiStatus, placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.15))
|
||||
emojiRegularStatusContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor)
|
||||
emojiExpandedStatusContent = .emojiStatus(status: emojiStatus, size: CGSize(width: 32.0, height: 32.0), placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.15))
|
||||
}
|
||||
|
||||
let iconSize = self.titleCredibilityIconView.update(
|
||||
@ -2439,13 +2447,50 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, currentEmojiStatus, false)
|
||||
strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFile, strongSelf.emojiStatusPackTitle, false)
|
||||
},
|
||||
longTapAction: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
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: {},
|
||||
@ -2462,7 +2507,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, true)
|
||||
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFile, strongSelf.emojiStatusPackTitle, true)
|
||||
},
|
||||
longTapAction: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
|
||||
@ -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 {
|
||||
return
|
||||
}
|
||||
@ -3080,26 +3080,36 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
} else {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
let source: PremiumSource
|
||||
if let peerStatus = peerStatus {
|
||||
source = .emojiStatus(strongSelf.peerId, peerStatus.fileId)
|
||||
} else {
|
||||
source = .profile(strongSelf.peerId)
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
|
||||
guard !premiumConfiguration.isPremiumDisabled else {
|
||||
return
|
||||
}
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
|
||||
if !premiumConfiguration.isPremiumDisabled {
|
||||
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)
|
||||
}
|
||||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let emojiFile = emojiStatusFile {
|
||||
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
|
||||
|
||||
@ -345,7 +345,6 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
let dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
self.dustNode = dustNode
|
||||
self.textNode.supernode?.insertSubnode(dustNode, aboveSubnode: self.textNode)
|
||||
|
||||
}
|
||||
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) })
|
||||
|
||||
@ -1501,8 +1501,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSource = .deeplink(reference)
|
||||
case let .profile(peerId):
|
||||
mappedSource = .profile(peerId)
|
||||
case let .emojiStatus(peerId, fileId):
|
||||
mappedSource = .emojiStatus(peerId, fileId)
|
||||
case let .emojiStatus(peerId, fileId, file, packTitle):
|
||||
mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
|
||||
}
|
||||
return PremiumIntroScreen(context: context, source: mappedSource)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user