Various improvements

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

View File

@ -7973,5 +7973,7 @@ Sorry for the inconvenience.";
"Settings.ChangeProfilePhoto" = "Change Profile Photo";
"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";

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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 {

View File

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

View File

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

View File

@ -155,6 +155,10 @@ public extension ContainerViewLayout {
return self.size.width > self.size.height ? .landscape : .portrait
}
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)
}

View File

@ -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 {

View File

@ -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? {

View File

@ -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)
}

View File

@ -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",

View File

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

View File

@ -13,41 +13,6 @@ import TelegramCore
private let sceneVersion: Int = 3
private 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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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(

View File

@ -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 {

View File

@ -3051,7 +3051,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}))
}
self.headerNode.displayPremiumIntro = { [weak self] sourceView, _, _ in
self.headerNode.displayPremiumIntro = { [weak self] sourceView, _, _, _, _ in
guard let strongSelf = self else {
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

View File

@ -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) })

View File

@ -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)
}