mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '15fd6d7b37d0a2bbd522cf22ec0cdfdb595144e3'
This commit is contained in:
commit
72ddbbbdcf
@ -13870,6 +13870,12 @@ Sorry for the inconvenience.";
|
|||||||
"Stars.Intro.Transaction.PaidMessage_1" = "Fee for %@ Message";
|
"Stars.Intro.Transaction.PaidMessage_1" = "Fee for %@ Message";
|
||||||
"Stars.Intro.Transaction.PaidMessage_any" = "Fee for %@ Messages";
|
"Stars.Intro.Transaction.PaidMessage_any" = "Fee for %@ Messages";
|
||||||
|
|
||||||
|
"Stars.Transaction.TelegramPremium_1" = "Premium for %@ Month";
|
||||||
|
"Stars.Transaction.TelegramPremium_any" = "Premium for %@ Months";
|
||||||
|
|
||||||
|
"Stars.Intro.Transaction.TelegramPremium_1" = "Premium for %@ Month";
|
||||||
|
"Stars.Intro.Transaction.TelegramPremium_any" = "Premium for %@ Months";
|
||||||
|
|
||||||
"Stars.Purchase.SendMessageInfo" = "Buy Stars to send a message to **%@**.";
|
"Stars.Purchase.SendMessageInfo" = "Buy Stars to send a message to **%@**.";
|
||||||
"Stars.Purchase.SendGroupMessageInfo" = "Buy Stars to send a message in **%@**.";
|
"Stars.Purchase.SendGroupMessageInfo" = "Buy Stars to send a message in **%@**.";
|
||||||
|
|
||||||
|
@ -629,6 +629,7 @@ private class SendStarsButtonView: HighlightTrackingButton, TGPhotoSendStarsButt
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.backgroundView = UIView()
|
self.backgroundView = UIView()
|
||||||
|
self.backgroundView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.textNode = ImmediateAnimatedCountLabelNode()
|
self.textNode = ImmediateAnimatedCountLabelNode()
|
||||||
self.textNode.isUserInteractionEnabled = false
|
self.textNode.isUserInteractionEnabled = false
|
||||||
@ -654,12 +655,22 @@ private class SendStarsButtonView: HighlightTrackingButton, TGPhotoSendStarsButt
|
|||||||
self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
print()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed() {
|
||||||
|
self.pressed?()
|
||||||
|
}
|
||||||
|
|
||||||
func updateFrame(_ frame: CGRect) {
|
func updateFrame(_ frame: CGRect) {
|
||||||
let transition: ContainedViewLayoutTransition
|
let transition: ContainedViewLayoutTransition
|
||||||
if self.frame.width.isZero {
|
if self.frame.width.isZero {
|
||||||
|
@ -1322,7 +1322,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let proceed: (Bool) -> Void = { convertToJpeg in
|
let proceed: (Bool) -> Void = { [weak self] convertToJpeg in
|
||||||
let signals: [Any]!
|
let signals: [Any]!
|
||||||
switch controller.subject {
|
switch controller.subject {
|
||||||
case .assets:
|
case .assets:
|
||||||
@ -1340,6 +1340,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||||||
completion()
|
completion()
|
||||||
self?.controller?.dismiss(animated: animated)
|
self?.controller?.dismiss(animated: animated)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Queue.mainQueue().after(1.5) {
|
||||||
|
controller.isDismissing = false
|
||||||
|
controller.completed = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if asFile && hasHeic {
|
if asFile && hasHeic {
|
||||||
|
@ -886,12 +886,6 @@ struct PremiumIntroConfiguration {
|
|||||||
perks = PremiumIntroConfiguration.defaultValue.perks
|
perks = PremiumIntroConfiguration.defaultValue.perks
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
if !perks.contains(.paidMessages) {
|
|
||||||
perks.append(.paidMessages)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var businessPerks: [PremiumPerk] = []
|
var businessPerks: [PremiumPerk] = []
|
||||||
if let values = data["business_promo_order"] as? [String] {
|
if let values = data["business_promo_order"] as? [String] {
|
||||||
for value in values {
|
for value in values {
|
||||||
|
@ -356,7 +356,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
|
|||||||
},
|
},
|
||||||
openPremiumInfo: {
|
openPremiumInfo: {
|
||||||
var replaceImpl: ((ViewController) -> Void)?
|
var replaceImpl: ((ViewController) -> Void)?
|
||||||
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .paidMessages, forceDark: false, action: {
|
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .messagePrivacy, forceDark: false, action: {
|
||||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .paidMessages, forceDark: false, dismissed: nil)
|
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .paidMessages, forceDark: false, dismissed: nil)
|
||||||
replaceImpl?(controller)
|
replaceImpl?(controller)
|
||||||
}, dismissed: nil)
|
}, dismissed: nil)
|
||||||
|
@ -1328,9 +1328,11 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
|||||||
if !self.inputFieldNode.text.isEmpty {
|
if !self.inputFieldNode.text.isEmpty {
|
||||||
count += 1
|
count += 1
|
||||||
}
|
}
|
||||||
|
var chargingPeers: [EnginePeer] = []
|
||||||
var totalAmount: StarsAmount = .zero
|
var totalAmount: StarsAmount = .zero
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
if let stars = requiresStars[peer.id] {
|
if let stars = requiresStars[peer.id] {
|
||||||
|
chargingPeers.append(peer)
|
||||||
totalAmount = totalAmount + StarsAmount(value: stars, nanos: 0)
|
totalAmount = totalAmount + StarsAmount(value: stars, nanos: 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1339,7 +1341,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
|||||||
context: nil,
|
context: nil,
|
||||||
presentationData: self.presentationData,
|
presentationData: self.presentationData,
|
||||||
updatedPresentationData: nil,
|
updatedPresentationData: nil,
|
||||||
peers: peers,
|
peers: chargingPeers,
|
||||||
count: count,
|
count: count,
|
||||||
amount: totalAmount,
|
amount: totalAmount,
|
||||||
totalAmount: totalAmount,
|
totalAmount: totalAmount,
|
||||||
|
@ -352,7 +352,8 @@ func _internal_updateGlobalPrivacySettings(account: Account, settings: GlobalPri
|
|||||||
var noncontactPeersPaidStars: Int64?
|
var noncontactPeersPaidStars: Int64?
|
||||||
switch settings.nonContactChatsPrivacy {
|
switch settings.nonContactChatsPrivacy {
|
||||||
case .everybody:
|
case .everybody:
|
||||||
break
|
flags |= 1 << 5
|
||||||
|
noncontactPeersPaidStars = 0
|
||||||
case .requirePremium:
|
case .requirePremium:
|
||||||
flags |= 1 << 4
|
flags |= 1 << 4
|
||||||
case let .paidMessages(starsAmount):
|
case let .paidMessages(starsAmount):
|
||||||
|
@ -219,12 +219,15 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if gestureRecognizer.view === self.offsetContainer.view {
|
||||||
let location = gestureRecognizer.location(in: self.offsetContainer.view)
|
let location = gestureRecognizer.location(in: self.offsetContainer.view)
|
||||||
if let backgroundContent = self.backgroundContent, backgroundContent.frame.contains(location) {
|
if let backgroundContent = self.backgroundContent, backgroundContent.frame.contains(location) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
|
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
@ -366,7 +369,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe
|
|||||||
var estimatedValueOffset: CGFloat = 0.0
|
var estimatedValueOffset: CGFloat = 0.0
|
||||||
if groupsInCommonCount > 0 {
|
if groupsInCommonCount > 0 {
|
||||||
groupsValueText = NSMutableAttributedString(string: item.presentationData.strings.Chat_NonContactUser_GroupsCount(groupsInCommonCount), font: Font.semibold(13.0), textColor: primaryTextColor)
|
groupsValueText = NSMutableAttributedString(string: item.presentationData.strings.Chat_NonContactUser_GroupsCount(groupsInCommonCount), font: Font.semibold(13.0), textColor: primaryTextColor)
|
||||||
estimatedValueOffset = avatarImageSize + CGFloat(min(2, max(0, item.groupsInCommonCount - 1))) * avatarSpacing + 4.0
|
estimatedValueOffset = avatarImageSize + CGFloat(min(2, max(0, item.groupsInCommonCount - 1))) * avatarSpacing + 4.0 + 10.0
|
||||||
} else {
|
} else {
|
||||||
groupsValueText = NSMutableAttributedString(string: "", font: Font.semibold(13.0), textColor: primaryTextColor)
|
groupsValueText = NSMutableAttributedString(string: "", font: Font.semibold(13.0), textColor: primaryTextColor)
|
||||||
}
|
}
|
||||||
@ -380,7 +383,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe
|
|||||||
backgroundSize.height += groupsValueLayoutAndApply?.0.size.height ?? 0.0
|
backgroundSize.height += groupsValueLayoutAndApply?.0.size.height ?? 0.0
|
||||||
|
|
||||||
maxTitleWidth = max(maxTitleWidth, groupsTitleLayoutAndApply?.0.size.width ?? 0)
|
maxTitleWidth = max(maxTitleWidth, groupsTitleLayoutAndApply?.0.size.width ?? 0)
|
||||||
maxValueWidth = max(maxValueWidth, groupsValueLayoutAndApply?.0.size.width ?? 0 + estimatedValueOffset)
|
maxValueWidth = max(maxValueWidth, (groupsValueLayoutAndApply?.0.size.width ?? 0) + estimatedValueOffset)
|
||||||
} else {
|
} else {
|
||||||
groupsTitleLayoutAndApply = nil
|
groupsTitleLayoutAndApply = nil
|
||||||
groupsValueLayoutAndApply = nil
|
groupsValueLayoutAndApply = nil
|
||||||
@ -474,15 +477,15 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe
|
|||||||
|
|
||||||
var attributeMidpoints: [CGFloat] = []
|
var attributeMidpoints: [CGFloat] = []
|
||||||
|
|
||||||
func appendAttributeMidpoint(titleLayout: TextNodeLayout?, valueLayout: TextNodeLayout?) {
|
func appendAttributeMidpoint(titleLayout: TextNodeLayout?, valueLayout: TextNodeLayout?, valueOffset: CGFloat = 0.0) {
|
||||||
if let valueLayout {
|
if let valueLayout {
|
||||||
let midpoint = backgroundSize.width - horizontalContentInset - valueLayout.size.width - attributeSpacing / 2.0
|
let midpoint = backgroundSize.width - horizontalContentInset - valueLayout.size.width - valueOffset - attributeSpacing / 2.0
|
||||||
attributeMidpoints.append(midpoint)
|
attributeMidpoints.append(midpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appendAttributeMidpoint(titleLayout: phoneCountryTitleLayoutAndApply?.0, valueLayout: phoneCountryValueLayoutAndApply?.0)
|
appendAttributeMidpoint(titleLayout: phoneCountryTitleLayoutAndApply?.0, valueLayout: phoneCountryValueLayoutAndApply?.0)
|
||||||
appendAttributeMidpoint(titleLayout: registrationDateTitleLayoutAndApply?.0, valueLayout: registrationDateValueLayoutAndApply?.0)
|
appendAttributeMidpoint(titleLayout: registrationDateTitleLayoutAndApply?.0, valueLayout: registrationDateValueLayoutAndApply?.0)
|
||||||
appendAttributeMidpoint(titleLayout: groupsTitleLayoutAndApply?.0, valueLayout: groupsValueLayoutAndApply?.0)
|
appendAttributeMidpoint(titleLayout: groupsTitleLayoutAndApply?.0, valueLayout: groupsValueLayoutAndApply?.0, valueOffset: estimatedValueOffset)
|
||||||
|
|
||||||
let middleX = floorToScreenPixels(attributeMidpoints.min() ?? backgroundSize.width / 2.0)
|
let middleX = floorToScreenPixels(attributeMidpoints.min() ?? backgroundSize.width / 2.0)
|
||||||
|
|
||||||
|
@ -2678,7 +2678,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
convertToStarsImpl = { [weak self] in
|
convertToStarsImpl = { [weak self] in
|
||||||
guard let self, let arguments = self.subject.arguments, let reference = arguments.reference, let fromPeerName = arguments.fromPeerName, let convertStars = arguments.convertStars, let navigationController = self.navigationController as? NavigationController else {
|
guard let self, let starsContext = context.starsContext, let arguments = self.subject.arguments, let reference = arguments.reference, let fromPeerName = arguments.fromPeerName, let convertStars = arguments.convertStars, let navigationController = self.navigationController as? NavigationController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2725,6 +2725,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
if let navigationController {
|
if let navigationController {
|
||||||
Queue.mainQueue().after(0.5) {
|
Queue.mainQueue().after(0.5) {
|
||||||
|
starsContext.load(force: true)
|
||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
if isChannelGift {
|
if isChannelGift {
|
||||||
text = presentationData.strings.Gift_Convert_Success_ChannelText(presentationData.strings.Gift_Convert_Success_ChannelText_Stars(Int32(convertStars))).string
|
text = presentationData.strings.Gift_Convert_Success_ChannelText(presentationData.strings.Gift_Convert_Success_ChannelText_Stars(Int32(convertStars))).string
|
||||||
|
@ -1452,7 +1452,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
inputActionButtonMode = .send
|
inputActionButtonMode = .send
|
||||||
} else {
|
} else {
|
||||||
if self.textFieldExternalState.hasText {
|
if self.textFieldExternalState.hasText {
|
||||||
if let sendPaidMessageStars = component.sendPaidMessageStars {
|
if let sendPaidMessageStars = component.sendPaidMessageStars, "".isEmpty {
|
||||||
inputActionButtonMode = .stars(sendPaidMessageStars.value)
|
inputActionButtonMode = .stars(sendPaidMessageStars.value)
|
||||||
} else {
|
} else {
|
||||||
inputActionButtonMode = .send
|
inputActionButtonMode = .send
|
||||||
|
@ -22,6 +22,7 @@ swift_library(
|
|||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||||
"//submodules/Utils/LokiRng",
|
"//submodules/Utils/LokiRng",
|
||||||
|
"//submodules/TextFormat",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -0,0 +1,805 @@
|
|||||||
|
import Foundation
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import ComponentDisplayAdapters
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
import SwiftSignalKit
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
import LokiRng
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
|
private final class PatternContentsTarget: MultiAnimationRenderTarget {
|
||||||
|
private let imageUpdated: (Bool) -> Void
|
||||||
|
|
||||||
|
init(imageUpdated: @escaping (Bool) -> Void) {
|
||||||
|
self.imageUpdated = imageUpdated
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func transitionToContents(_ contents: AnyObject, didLoop: Bool) {
|
||||||
|
let hadContents = self.contents != nil
|
||||||
|
self.contents = contents
|
||||||
|
self.imageUpdated(hadContents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func windowFunction(t: CGFloat) -> CGFloat {
|
||||||
|
return bezierPoint(0.6, 0.0, 0.4, 1.0, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func patternScaleValueAt(fraction: CGFloat, t: CGFloat, reverse: Bool) -> CGFloat {
|
||||||
|
let windowSize: CGFloat = 0.8
|
||||||
|
|
||||||
|
let effectiveT: CGFloat
|
||||||
|
let windowStartOffset: CGFloat
|
||||||
|
let windowEndOffset: CGFloat
|
||||||
|
if reverse {
|
||||||
|
effectiveT = 1.0 - t
|
||||||
|
windowStartOffset = 1.0
|
||||||
|
windowEndOffset = -windowSize
|
||||||
|
} else {
|
||||||
|
effectiveT = t
|
||||||
|
windowStartOffset = -windowSize
|
||||||
|
windowEndOffset = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let windowPosition = (1.0 - fraction) * windowStartOffset + fraction * windowEndOffset
|
||||||
|
let windowT = max(0.0, min(windowSize, effectiveT - windowPosition)) / windowSize
|
||||||
|
let localT = 1.0 - windowFunction(t: windowT)
|
||||||
|
|
||||||
|
return localT
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class PeerInfoGiftsCoverComponent: Component {
|
||||||
|
public let context: AccountContext
|
||||||
|
public let peerId: EnginePeer.Id
|
||||||
|
public let giftsContext: ProfileGiftsContext
|
||||||
|
public let hasBackground: Bool
|
||||||
|
public let avatarCenter: CGPoint
|
||||||
|
public let avatarScale: CGFloat
|
||||||
|
public let defaultHeight: CGFloat
|
||||||
|
public let avatarTransitionFraction: CGFloat
|
||||||
|
public let patternTransitionFraction: CGFloat
|
||||||
|
public let hasButtons: Bool
|
||||||
|
|
||||||
|
public init(
|
||||||
|
context: AccountContext,
|
||||||
|
peerId: EnginePeer.Id,
|
||||||
|
giftsContext: ProfileGiftsContext,
|
||||||
|
hasBackground: Bool,
|
||||||
|
avatarCenter: CGPoint,
|
||||||
|
avatarScale: CGFloat,
|
||||||
|
defaultHeight: CGFloat,
|
||||||
|
avatarTransitionFraction: CGFloat,
|
||||||
|
patternTransitionFraction: CGFloat,
|
||||||
|
hasButtons: Bool
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.peerId = peerId
|
||||||
|
self.giftsContext = giftsContext
|
||||||
|
self.hasBackground = hasBackground
|
||||||
|
self.avatarCenter = avatarCenter
|
||||||
|
self.avatarScale = avatarScale
|
||||||
|
self.defaultHeight = defaultHeight
|
||||||
|
self.avatarTransitionFraction = avatarTransitionFraction
|
||||||
|
self.patternTransitionFraction = patternTransitionFraction
|
||||||
|
self.hasButtons = hasButtons
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: PeerInfoGiftsCoverComponent, rhs: PeerInfoGiftsCoverComponent) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.peerId != rhs.peerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.hasBackground != rhs.hasBackground {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.avatarCenter != rhs.avatarCenter {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.avatarScale != rhs.avatarScale {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.defaultHeight != rhs.defaultHeight {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.patternTransitionFraction != rhs.patternTransitionFraction {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.hasButtons != rhs.hasButtons {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private let avatarBackgroundPatternContentsLayer: SimpleGradientLayer
|
||||||
|
private let avatarBackgroundPatternMaskLayer: SimpleLayer
|
||||||
|
private let avatarBackgroundGradientLayer: SimpleGradientLayer
|
||||||
|
private let backgroundPatternContainer: UIView
|
||||||
|
|
||||||
|
private var currentSize: CGSize?
|
||||||
|
private var component: PeerInfoGiftsCoverComponent?
|
||||||
|
private var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var giftsDisposable: Disposable?
|
||||||
|
private var gifts: [ProfileGiftsContext.State.StarGift] = []
|
||||||
|
private var appliedGiftIds: [Int64] = []
|
||||||
|
|
||||||
|
private var iconLayers: [AnyHashable: GiftIconLayer] = [:]
|
||||||
|
|
||||||
|
private var iconPositions: [PositionGenerator.Position] = []
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
self.avatarBackgroundGradientLayer = SimpleGradientLayer()
|
||||||
|
self.avatarBackgroundGradientLayer.opacity = 0.0
|
||||||
|
|
||||||
|
self.avatarBackgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
||||||
|
self.avatarBackgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
||||||
|
self.avatarBackgroundGradientLayer.type = .radial
|
||||||
|
|
||||||
|
self.avatarBackgroundPatternContentsLayer = SimpleGradientLayer()
|
||||||
|
self.avatarBackgroundPatternContentsLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
||||||
|
self.avatarBackgroundPatternContentsLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
||||||
|
self.avatarBackgroundPatternContentsLayer.type = .radial
|
||||||
|
|
||||||
|
self.avatarBackgroundPatternMaskLayer = SimpleLayer()
|
||||||
|
self.backgroundPatternContainer = UIView()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.avatarBackgroundPatternContentsLayer.mask = self.avatarBackgroundPatternMaskLayer
|
||||||
|
self.layer.addSublayer(self.avatarBackgroundPatternContentsLayer)
|
||||||
|
|
||||||
|
self.addSubview(self.backgroundPatternContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.giftsDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isUpdating = false
|
||||||
|
func update(component: PeerInfoGiftsCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.isUpdating = true
|
||||||
|
defer {
|
||||||
|
self.isUpdating = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousComponent = self.component
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
let previousCurrentSize = self.currentSize
|
||||||
|
self.currentSize = availableSize
|
||||||
|
|
||||||
|
let iconSize = CGSize(width: 32.0, height: 32.0)
|
||||||
|
|
||||||
|
let giftIds = self.gifts.map { gift in
|
||||||
|
if case let .unique(gift) = gift.gift {
|
||||||
|
return gift.id
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if previousCurrentSize?.width != availableSize.width || (previousComponent != nil && previousComponent?.hasBackground != component.hasBackground) || self.appliedGiftIds != giftIds {
|
||||||
|
var excludeRects: [CGRect] = []
|
||||||
|
excludeRects.append(CGRect(origin: .zero, size: CGSize(width: 50.0, height: 90.0)))
|
||||||
|
excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - 105.0, y: 0.0), size: CGSize(width: 105.0, height: 90.0)))
|
||||||
|
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - 390.0) / 2.0), y: 0.0), size: CGSize(width: 390.0, height: 50.0)))
|
||||||
|
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - 280.0) / 2.0), y: component.avatarCenter.y + 56.0), size: CGSize(width: 280.0, height: 65.0)))
|
||||||
|
if component.hasButtons {
|
||||||
|
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - 81.0), size: CGSize(width: availableSize.width, height: 81.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let positionGenerator = PositionGenerator(
|
||||||
|
containerSize: availableSize,
|
||||||
|
avatarFrame: CGSize(width: 100, height: 100).centered(around: component.avatarCenter),
|
||||||
|
minDistance: 75.0,
|
||||||
|
maxDistance: availableSize.width / 2.0,
|
||||||
|
padding: 12.0,
|
||||||
|
seed: UInt(Date().timeIntervalSince1970),
|
||||||
|
excludeRects: excludeRects
|
||||||
|
)
|
||||||
|
self.iconPositions = positionGenerator.generatePositions(count: 9, viewSize: iconSize)
|
||||||
|
}
|
||||||
|
self.appliedGiftIds = giftIds
|
||||||
|
|
||||||
|
if self.giftsDisposable == nil {
|
||||||
|
self.giftsDisposable = combineLatest(
|
||||||
|
queue: Queue.mainQueue(),
|
||||||
|
component.giftsContext.state,
|
||||||
|
component.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId))
|
||||||
|
|> map { peer -> Int64? in
|
||||||
|
if case let .user(user) = peer, case let .starGift(id, _, _, _, _, _, _, _, _) = user.emojiStatus?.content {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
).start(next: { [weak self] state, giftStatusId in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let pinnedGifts = state.gifts.filter { gift in
|
||||||
|
if gift.pinnedToTop {
|
||||||
|
if case let .unique(uniqueGift) = gift.gift {
|
||||||
|
return uniqueGift.id != giftStatusId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
self.gifts = pinnedGifts
|
||||||
|
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let avatarPatternFrame = CGSize(width: 380.0, height: floor(component.defaultHeight * 1.0)).centered(around: component.avatarCenter)
|
||||||
|
transition.setFrame(layer: self.avatarBackgroundPatternContentsLayer, frame: avatarPatternFrame)
|
||||||
|
|
||||||
|
self.avatarBackgroundPatternContentsLayer.colors = [
|
||||||
|
UIColor.red.withAlphaComponent(0.6).cgColor,
|
||||||
|
UIColor.red.withAlphaComponent(0.0).cgColor
|
||||||
|
]
|
||||||
|
|
||||||
|
let backgroundPatternContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height), size: CGSize(width: availableSize.width, height: 0.0))
|
||||||
|
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame)
|
||||||
|
transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction)
|
||||||
|
|
||||||
|
var validIds = Set<AnyHashable>()
|
||||||
|
var index = 0
|
||||||
|
for gift in self.gifts.prefix(9) {
|
||||||
|
let id: AnyHashable
|
||||||
|
if case let .unique(uniqueGift) = gift.gift {
|
||||||
|
id = uniqueGift.slug
|
||||||
|
} else {
|
||||||
|
id = index
|
||||||
|
}
|
||||||
|
validIds.insert(id)
|
||||||
|
|
||||||
|
var iconTransition = transition
|
||||||
|
let iconPosition = self.iconPositions[index]
|
||||||
|
let iconLayer: GiftIconLayer
|
||||||
|
if let current = self.iconLayers[id] {
|
||||||
|
iconLayer = current
|
||||||
|
} else {
|
||||||
|
iconTransition = .immediate
|
||||||
|
iconLayer = GiftIconLayer(context: component.context, gift: gift, size: iconSize, glowing: component.hasBackground)
|
||||||
|
iconLayer.startHovering()
|
||||||
|
self.iconLayers[id] = iconLayer
|
||||||
|
self.layer.addSublayer(iconLayer)
|
||||||
|
|
||||||
|
iconLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
iconLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
iconLayer.glowing = component.hasBackground
|
||||||
|
|
||||||
|
let centerPosition = component.avatarCenter
|
||||||
|
let finalPosition = iconPosition.center.offsetBy(dx: component.avatarCenter.x, dy: component.avatarCenter.y)
|
||||||
|
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: 0.0, reverse: false)
|
||||||
|
|
||||||
|
func interpolateRect(from: CGPoint, to: CGPoint, t: CGFloat) -> CGPoint {
|
||||||
|
let clampedT = max(0, min(1, t))
|
||||||
|
|
||||||
|
let interpolatedX = from.x + (to.x - from.x) * clampedT
|
||||||
|
let interpolatedY = from.y + (to.y - from.y) * clampedT
|
||||||
|
|
||||||
|
return CGPoint(
|
||||||
|
x: interpolatedX,
|
||||||
|
y: interpolatedY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let effectivePosition = interpolateRect(from: finalPosition, to: centerPosition, t: itemScaleFraction)
|
||||||
|
|
||||||
|
iconTransition.setBounds(layer: iconLayer, bounds: CGRect(origin: .zero, size: iconSize))
|
||||||
|
iconTransition.setPosition(layer: iconLayer, position: effectivePosition)
|
||||||
|
iconTransition.setScale(layer: iconLayer, scale: iconPosition.scale * (1.0 - itemScaleFraction))
|
||||||
|
iconTransition.setAlpha(layer: iconLayer, alpha: 1.0 - itemScaleFraction)
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeIds: [AnyHashable] = []
|
||||||
|
for (id, layer) in self.iconLayers {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeIds.append(id)
|
||||||
|
layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||||
|
layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||||
|
layer.removeFromSuperlayer()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in removeIds {
|
||||||
|
self.iconLayers.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class PositionGenerator {
|
||||||
|
private let containerSize: CGSize
|
||||||
|
private let avatarFrame: CGRect
|
||||||
|
private let padding: CGFloat
|
||||||
|
private let minDistance: CGFloat
|
||||||
|
private let maxDistance: CGFloat
|
||||||
|
private let rng: LokiRng
|
||||||
|
|
||||||
|
private let excludeRects: [CGRect]
|
||||||
|
|
||||||
|
struct Position {
|
||||||
|
let center: CGPoint
|
||||||
|
let scale: CGFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
containerSize: CGSize,
|
||||||
|
avatarFrame: CGRect,
|
||||||
|
minDistance: CGFloat,
|
||||||
|
maxDistance: CGFloat,
|
||||||
|
padding: CGFloat,
|
||||||
|
seed: UInt,
|
||||||
|
excludeRects: [CGRect] = []
|
||||||
|
) {
|
||||||
|
self.containerSize = containerSize
|
||||||
|
self.avatarFrame = avatarFrame
|
||||||
|
self.minDistance = minDistance
|
||||||
|
self.maxDistance = maxDistance
|
||||||
|
self.padding = padding
|
||||||
|
self.rng = LokiRng(seed0: seed, seed1: 0, seed2: 0)
|
||||||
|
self.excludeRects = excludeRects
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatePositions(count: Int, viewSize: CGSize) -> [Position] {
|
||||||
|
let safeCount = min(max(count, 1), 12) // Ensure between 1 and 12
|
||||||
|
var positions: [Position] = []
|
||||||
|
|
||||||
|
let distanceRanges = calculateDistanceRanges(count: safeCount)
|
||||||
|
|
||||||
|
for i in 0..<safeCount {
|
||||||
|
let minDist = distanceRanges[i].0
|
||||||
|
let maxDist = distanceRanges[i].1
|
||||||
|
let isEven = i % 2 == 0
|
||||||
|
|
||||||
|
var attempts = 0
|
||||||
|
let maxAttempts = 20
|
||||||
|
var currentMaxDist = maxDist
|
||||||
|
|
||||||
|
var result: CGPoint?
|
||||||
|
|
||||||
|
while result == nil && attempts < maxAttempts {
|
||||||
|
attempts += 1
|
||||||
|
|
||||||
|
if let position = generateSinglePosition(
|
||||||
|
viewSize: viewSize,
|
||||||
|
minDist: minDist,
|
||||||
|
maxDist: currentMaxDist,
|
||||||
|
rightSide: !isEven
|
||||||
|
) {
|
||||||
|
let isFarEnough = positions.isEmpty || positions.allSatisfy { existingPosition in
|
||||||
|
let distance = hypot(position.x - existingPosition.center.x, position.y - existingPosition.center.y)
|
||||||
|
let minRequiredDistance = max(viewSize.width, viewSize.height) / 2 + max(viewSize.width, viewSize.height) / 2 + padding
|
||||||
|
return distance > minRequiredDistance
|
||||||
|
}
|
||||||
|
if isFarEnough {
|
||||||
|
result = position
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempts % 5 == 0 && result == nil {
|
||||||
|
currentMaxDist *= 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
if let lastChancePosition = self.generateSinglePosition(
|
||||||
|
viewSize: viewSize,
|
||||||
|
minDist: minDist,
|
||||||
|
maxDist: maxDist * 2.0,
|
||||||
|
rightSide: !isEven
|
||||||
|
) {
|
||||||
|
result = lastChancePosition
|
||||||
|
} else {
|
||||||
|
let defaultX = self.avatarFrame.center.x + (isEven ? -1 : 1) * (minDist + CGFloat(i * 20))
|
||||||
|
let defaultY = self.avatarFrame.center.y + CGFloat(i * 15)
|
||||||
|
let defaultPosition = CGPoint(x: defaultX, y: defaultY)
|
||||||
|
|
||||||
|
result = defaultPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let result {
|
||||||
|
let distance = hypot(result.x - self.avatarFrame.center.x, result.y - self.avatarFrame.center.y)
|
||||||
|
let baseScale = min(1.0, max(0.77, 1.0 - (distance - 75.0) / 75.0))
|
||||||
|
|
||||||
|
let randomFactor = 0.14 + (1.0 - baseScale) * 0.2
|
||||||
|
let randomValue = -randomFactor + CGFloat(self.rng.next()) * 2.0 * randomFactor
|
||||||
|
|
||||||
|
let finalScale = min(1.2, max(baseScale * 0.65, baseScale + randomValue))
|
||||||
|
positions.append(Position(center: result, scale: finalScale))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions.map {
|
||||||
|
Position(center: $0.center.offsetBy(dx: -self.avatarFrame.center.x, dy: -self.avatarFrame.center.y), scale: $0.scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func calculateDistanceRanges(count: Int) -> [(CGFloat, CGFloat)] {
|
||||||
|
var ranges: [(CGFloat, CGFloat)] = []
|
||||||
|
|
||||||
|
let totalRange = self.maxDistance - self.minDistance
|
||||||
|
for _ in 0..<4 {
|
||||||
|
let min = self.minDistance
|
||||||
|
let max = self.minDistance + (totalRange * 0.12)
|
||||||
|
ranges.append((min, max))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..<4 {
|
||||||
|
let min = self.minDistance + (totalRange * 0.19)
|
||||||
|
let max = self.minDistance + (totalRange * 0.55)
|
||||||
|
ranges.append((min, max))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..<4 {
|
||||||
|
let min = self.minDistance + (totalRange * 0.6)
|
||||||
|
let max = self.minDistance + (totalRange * 0.9)
|
||||||
|
ranges.append((min, max))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateSinglePosition(viewSize: CGSize, minDist: CGFloat, maxDist: CGFloat, rightSide: Bool) -> CGPoint? {
|
||||||
|
let avatarCenter = avatarFrame.center
|
||||||
|
|
||||||
|
for _ in 0..<50 {
|
||||||
|
let baseAngle: CGFloat
|
||||||
|
let angleSpread: CGFloat
|
||||||
|
|
||||||
|
if rightSide {
|
||||||
|
baseAngle = 0
|
||||||
|
angleSpread = .pi / 2
|
||||||
|
} else {
|
||||||
|
baseAngle = .pi
|
||||||
|
angleSpread = .pi / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
let angleOffset = (CGFloat(rng.next()) * 2.0 - 1.0) * angleSpread
|
||||||
|
let angle = baseAngle + angleOffset
|
||||||
|
|
||||||
|
let distance = minDist + CGFloat(rng.next()) * (maxDist - minDist)
|
||||||
|
|
||||||
|
let x = avatarCenter.x + cos(angle) * distance
|
||||||
|
let y = avatarCenter.y + sin(angle) * distance
|
||||||
|
|
||||||
|
let position = CGPoint(x: x, y: y)
|
||||||
|
|
||||||
|
let viewFrame = CGRect(
|
||||||
|
x: position.x - viewSize.width / 2,
|
||||||
|
y: position.y - viewSize.height / 2,
|
||||||
|
width: viewSize.width,
|
||||||
|
height: viewSize.height
|
||||||
|
)
|
||||||
|
|
||||||
|
if isFrameWithinBounds(viewFrame) && !isFrameInExclusionZone(viewFrame) {
|
||||||
|
return CGPoint(x: round(position.x), y: round(position.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isFrameWithinBounds(_ frame: CGRect) -> Bool {
|
||||||
|
return frame.minX >= self.padding &&
|
||||||
|
frame.minY >= self.padding &&
|
||||||
|
frame.maxX <= self.containerSize.width - self.padding &&
|
||||||
|
frame.maxY <= self.containerSize.height - self.padding
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isFrameInExclusionZone(_ frame: CGRect) -> Bool {
|
||||||
|
if frame.intersects(avatarFrame) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let padding: CGFloat = -8.0
|
||||||
|
for excludeRect in self.excludeRects {
|
||||||
|
if frame.intersects(excludeRect.insetBy(dx: padding, dy: padding)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var shadowImage: UIImage? = {
|
||||||
|
return generateImage(CGSize(width: 44.0, height: 44.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [0.0, 0.3, 1.0]
|
||||||
|
let colors: [CGColor] = [UIColor(rgb: 0xffffff, alpha: 0.65).cgColor, UIColor(rgb: 0xffffff, alpha: 0.65).cgColor, UIColor(rgb: 0xffffff, alpha: 0.0).cgColor]
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
context.drawRadialGradient(gradient, startCenter: CGPoint(x: size.width / 2.0, y: size.height / 2.0), startRadius: 0.0, endCenter: CGPoint(x: size.width / 2.0, y: size.height / 2.0), endRadius: size.width / 2.0, options: .drawsAfterEndLocation)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
private final class StarsEffectLayer: SimpleLayer {
|
||||||
|
private let emitterLayer = CAEmitterLayer()
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSublayer(self.emitterLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(color: UIColor, size: CGSize) {
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
let emitter = CAEmitterCell()
|
||||||
|
emitter.name = "emitter"
|
||||||
|
emitter.contents = UIImage(bundleImageName: "Premium/Stars/Particle")?.cgImage
|
||||||
|
emitter.birthRate = 8.0
|
||||||
|
emitter.lifetime = 2.0
|
||||||
|
emitter.velocity = 0.1
|
||||||
|
emitter.scale = (size.width / 40.0) * 0.12
|
||||||
|
emitter.scaleRange = 0.02
|
||||||
|
emitter.alphaRange = 0.1
|
||||||
|
emitter.emissionRange = .pi * 2.0
|
||||||
|
|
||||||
|
let staticColors: [Any] = [
|
||||||
|
color.withAlphaComponent(0.0).cgColor,
|
||||||
|
color.withAlphaComponent(0.58).cgColor,
|
||||||
|
color.withAlphaComponent(0.58).cgColor,
|
||||||
|
color.withAlphaComponent(0.0).cgColor
|
||||||
|
]
|
||||||
|
let staticColorBehavior = CAEmitterCell.createEmitterBehavior(type: "colorOverLife")
|
||||||
|
staticColorBehavior.setValue(staticColors, forKey: "colors")
|
||||||
|
emitter.setValue([staticColorBehavior], forKey: "emitterBehaviors")
|
||||||
|
self.emitterLayer.emitterCells = [emitter]
|
||||||
|
}
|
||||||
|
|
||||||
|
private var color: UIColor?
|
||||||
|
|
||||||
|
func update(color: UIColor, size: CGSize) {
|
||||||
|
if self.color != color {
|
||||||
|
self.setup(color: color, size: size)
|
||||||
|
}
|
||||||
|
self.emitterLayer.seed = UInt32.random(in: .min ..< .max)
|
||||||
|
self.emitterLayer.emitterShape = .circle
|
||||||
|
self.emitterLayer.emitterSize = size
|
||||||
|
self.emitterLayer.emitterMode = .surface
|
||||||
|
self.emitterLayer.frame = CGRect(origin: .zero, size: size)
|
||||||
|
self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class GiftIconLayer: SimpleLayer {
|
||||||
|
private let context: AccountContext
|
||||||
|
private let gift: ProfileGiftsContext.State.StarGift
|
||||||
|
private let size: CGSize
|
||||||
|
var glowing: Bool {
|
||||||
|
didSet {
|
||||||
|
self.shadowLayer.opacity = self.glowing ? 1.0 : 0.0
|
||||||
|
|
||||||
|
let color: UIColor
|
||||||
|
if self.glowing {
|
||||||
|
color = .white
|
||||||
|
} else if let layerTintColor = self.shadowLayer.layerTintColor {
|
||||||
|
color = UIColor(cgColor: layerTintColor)
|
||||||
|
} else {
|
||||||
|
color = .white
|
||||||
|
}
|
||||||
|
|
||||||
|
let side = floor(self.size.width * 1.25)
|
||||||
|
let starsFrame = CGSize(width: side, height: side).centered(in: CGRect(origin: .zero, size: self.size))
|
||||||
|
self.starsLayer.frame = starsFrame
|
||||||
|
self.starsLayer.update(color: color, size: starsFrame.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let shadowLayer = SimpleLayer()
|
||||||
|
let starsLayer = StarsEffectLayer()
|
||||||
|
let animationLayer: InlineStickerItemLayer
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
guard let layer = layer as? GiftIconLayer else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = layer.context
|
||||||
|
let gift = layer.gift
|
||||||
|
let size = layer.size
|
||||||
|
let glowing = layer.glowing
|
||||||
|
|
||||||
|
var file: TelegramMediaFile?
|
||||||
|
var color: UIColor = .white
|
||||||
|
switch gift.gift {
|
||||||
|
case let .generic(gift):
|
||||||
|
file = gift.file
|
||||||
|
case let .unique(gift):
|
||||||
|
for attribute in gift.attributes {
|
||||||
|
if case let .model(_, fileValue, _) = attribute {
|
||||||
|
file = fileValue
|
||||||
|
} else if case let .backdrop(_, innerColor, _, _, _, _) = attribute {
|
||||||
|
color = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||||
|
interactivelySelectedFromPackId: nil,
|
||||||
|
fileId: file?.fileId.id ?? 0,
|
||||||
|
file: file
|
||||||
|
)
|
||||||
|
self.animationLayer = InlineStickerItemLayer(
|
||||||
|
context: .account(context),
|
||||||
|
userLocation: .other,
|
||||||
|
attemptSynchronousLoad: false,
|
||||||
|
emoji: emoji,
|
||||||
|
file: file,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
unique: true,
|
||||||
|
placeholderColor: UIColor.white.withAlphaComponent(0.2),
|
||||||
|
pointSize: CGSize(width: size.width * 2.0, height: size.height * 2.0),
|
||||||
|
loopCount: 1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.shadowLayer.contents = shadowImage?.cgImage
|
||||||
|
self.shadowLayer.layerTintColor = color.cgColor
|
||||||
|
self.shadowLayer.opacity = glowing ? 1.0 : 0.0
|
||||||
|
|
||||||
|
self.context = context
|
||||||
|
self.gift = gift
|
||||||
|
self.size = size
|
||||||
|
self.glowing = glowing
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
let side = floor(size.width * 1.25)
|
||||||
|
let starsFrame = CGSize(width: side, height: side).centered(in: CGRect(origin: .zero, size: size))
|
||||||
|
self.starsLayer.frame = starsFrame
|
||||||
|
self.starsLayer.update(color: glowing ? .white : color, size: starsFrame.size)
|
||||||
|
|
||||||
|
self.addSublayer(self.shadowLayer)
|
||||||
|
self.addSublayer(self.starsLayer)
|
||||||
|
self.addSublayer(self.animationLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AccountContext,
|
||||||
|
gift: ProfileGiftsContext.State.StarGift,
|
||||||
|
size: CGSize,
|
||||||
|
glowing: Bool
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.gift = gift
|
||||||
|
self.size = size
|
||||||
|
self.glowing = glowing
|
||||||
|
|
||||||
|
var file: TelegramMediaFile?
|
||||||
|
var color: UIColor = .white
|
||||||
|
switch gift.gift {
|
||||||
|
case let .generic(gift):
|
||||||
|
file = gift.file
|
||||||
|
case let .unique(gift):
|
||||||
|
for attribute in gift.attributes {
|
||||||
|
if case let .model(_, fileValue, _) = attribute {
|
||||||
|
file = fileValue
|
||||||
|
} else if case let .backdrop(_, innerColor, _, _, _, _) = attribute {
|
||||||
|
color = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||||
|
interactivelySelectedFromPackId: nil,
|
||||||
|
fileId: file?.fileId.id ?? 0,
|
||||||
|
file: file
|
||||||
|
)
|
||||||
|
self.animationLayer = InlineStickerItemLayer(
|
||||||
|
context: .account(context),
|
||||||
|
userLocation: .other,
|
||||||
|
attemptSynchronousLoad: false,
|
||||||
|
emoji: emoji,
|
||||||
|
file: file,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
unique: true,
|
||||||
|
placeholderColor: UIColor.white.withAlphaComponent(0.2),
|
||||||
|
pointSize: CGSize(width: size.width * 2.0, height: size.height * 2.0),
|
||||||
|
loopCount: 1
|
||||||
|
)
|
||||||
|
|
||||||
|
self.shadowLayer.contents = shadowImage?.cgImage
|
||||||
|
self.shadowLayer.layerTintColor = color.cgColor
|
||||||
|
self.shadowLayer.opacity = glowing ? 1.0 : 0.0
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
let side = floor(size.width * 1.25)
|
||||||
|
let starsFrame = CGSize(width: side, height: side).centered(in: CGRect(origin: .zero, size: size))
|
||||||
|
self.starsLayer.frame = starsFrame
|
||||||
|
self.starsLayer.update(color: glowing ? .white : color, size: starsFrame.size)
|
||||||
|
|
||||||
|
self.addSublayer(self.shadowLayer)
|
||||||
|
self.addSublayer(self.starsLayer)
|
||||||
|
self.addSublayer(self.animationLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSublayers() {
|
||||||
|
self.shadowLayer.frame = CGRect(origin: .zero, size: self.bounds.size).insetBy(dx: -4.0, dy: -4.0)
|
||||||
|
self.animationLayer.frame = CGRect(origin: .zero, size: self.bounds.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startHovering(distance: CGFloat = 3.0, duration: TimeInterval = 4.0, timingFunction: CAMediaTimingFunction = CAMediaTimingFunction(name: .easeInEaseOut)) {
|
||||||
|
let hoverAnimation = CABasicAnimation(keyPath: "transform.translation.y")
|
||||||
|
hoverAnimation.duration = duration
|
||||||
|
hoverAnimation.fromValue = -distance
|
||||||
|
hoverAnimation.toValue = distance
|
||||||
|
hoverAnimation.autoreverses = true
|
||||||
|
hoverAnimation.repeatCount = .infinity
|
||||||
|
hoverAnimation.timingFunction = timingFunction
|
||||||
|
hoverAnimation.beginTime = Double.random(in: 0.0 ..< 12.0)
|
||||||
|
hoverAnimation.isAdditive = true
|
||||||
|
self.add(hoverAnimation, forKey: "hover")
|
||||||
|
|
||||||
|
let glowAnimation = CABasicAnimation(keyPath: "transform.scale")
|
||||||
|
glowAnimation.duration = duration
|
||||||
|
glowAnimation.fromValue = 1.0
|
||||||
|
glowAnimation.toValue = 1.2
|
||||||
|
glowAnimation.autoreverses = true
|
||||||
|
glowAnimation.repeatCount = .infinity
|
||||||
|
glowAnimation.timingFunction = timingFunction
|
||||||
|
glowAnimation.beginTime = Double.random(in: 0.0 ..< 12.0)
|
||||||
|
self.shadowLayer.add(glowAnimation, forKey: "glow")
|
||||||
|
}
|
||||||
|
}
|
@ -836,6 +836,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||||||
starsState = .single(nil)
|
starsState = .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId)
|
||||||
|
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||||
accountsAndPeers,
|
accountsAndPeers,
|
||||||
@ -945,7 +947,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||||||
starsRevenueStatsContext: nil,
|
starsRevenueStatsContext: nil,
|
||||||
revenueStatsState: nil,
|
revenueStatsState: nil,
|
||||||
revenueStatsContext: nil,
|
revenueStatsContext: nil,
|
||||||
profileGiftsContext: nil,
|
profileGiftsContext: profileGiftsContext,
|
||||||
premiumGiftOptions: [],
|
premiumGiftOptions: [],
|
||||||
webAppPermissions: nil
|
webAppPermissions: nil
|
||||||
)
|
)
|
||||||
|
@ -102,6 +102,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
let backgroundBannerView: UIView
|
let backgroundBannerView: UIView
|
||||||
let backgroundCover = ComponentView<Empty>()
|
let backgroundCover = ComponentView<Empty>()
|
||||||
|
let giftsCover = ComponentView<Empty>()
|
||||||
var didSetupBackgroundCover = false
|
var didSetupBackgroundCover = false
|
||||||
let buttonsContainerNode: SparseNode
|
let buttonsContainerNode: SparseNode
|
||||||
let buttonsBackgroundNode: NavigationBackgroundNode
|
let buttonsBackgroundNode: NavigationBackgroundNode
|
||||||
@ -491,7 +492,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
private var currentStatusIcon: CredibilityIcon?
|
private var currentStatusIcon: CredibilityIcon?
|
||||||
|
|
||||||
private var currentPanelStatusData: PeerInfoStatusData?
|
private var currentPanelStatusData: PeerInfoStatusData?
|
||||||
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat {
|
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, profileGiftsContext: ProfileGiftsContext?, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat {
|
||||||
if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode {
|
if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode {
|
||||||
if let previous = self.appliedCustomNavigationContentNode {
|
if let previous = self.appliedCustomNavigationContentNode {
|
||||||
transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in
|
transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in
|
||||||
@ -2276,6 +2277,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
let backgroundCoverSubject: PeerInfoCoverComponent.Subject?
|
let backgroundCoverSubject: PeerInfoCoverComponent.Subject?
|
||||||
var backgroundCoverAnimateIn = false
|
var backgroundCoverAnimateIn = false
|
||||||
var backgroundDefaultHeight: CGFloat = 254.0
|
var backgroundDefaultHeight: CGFloat = 254.0
|
||||||
|
var hasBackground = false
|
||||||
if let status = peer?.emojiStatus, case .starGift = status.content {
|
if let status = peer?.emojiStatus, case .starGift = status.content {
|
||||||
backgroundCoverSubject = .status(status)
|
backgroundCoverSubject = .status(status)
|
||||||
if !self.didSetupBackgroundCover {
|
if !self.didSetupBackgroundCover {
|
||||||
@ -2287,8 +2289,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
if !buttonKeys.isEmpty {
|
if !buttonKeys.isEmpty {
|
||||||
backgroundDefaultHeight = 327.0
|
backgroundDefaultHeight = 327.0
|
||||||
}
|
}
|
||||||
|
hasBackground = true
|
||||||
} else if let peer {
|
} else if let peer {
|
||||||
backgroundCoverSubject = .peer(EnginePeer(peer))
|
backgroundCoverSubject = .peer(EnginePeer(peer))
|
||||||
|
if peer.profileColor != nil {
|
||||||
|
hasBackground = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
backgroundCoverSubject = nil
|
backgroundCoverSubject = nil
|
||||||
}
|
}
|
||||||
@ -2336,6 +2342,36 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let profileGiftsContext, let peer {
|
||||||
|
let giftsCoverSize = self.giftsCover.update(
|
||||||
|
transition: ComponentTransition(transition),
|
||||||
|
component: AnyComponent(PeerInfoGiftsCoverComponent(
|
||||||
|
context: self.context,
|
||||||
|
peerId: peer.id,
|
||||||
|
giftsContext: profileGiftsContext,
|
||||||
|
hasBackground: hasBackground,
|
||||||
|
avatarCenter: apparentAvatarFrame.center,
|
||||||
|
avatarScale: avatarScale,
|
||||||
|
defaultHeight: backgroundDefaultHeight,
|
||||||
|
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
|
||||||
|
patternTransitionFraction: buttonsTransitionFraction * backgroundTransitionFraction,
|
||||||
|
hasButtons: !buttonKeys.isEmpty
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: width + bannerInset * 2.0, height: apparentBackgroundHeight + bannerInset)
|
||||||
|
)
|
||||||
|
if let giftsCoverView = self.giftsCover.view as? PeerInfoGiftsCoverComponent.View {
|
||||||
|
if giftsCoverView.superview == nil {
|
||||||
|
self.backgroundBannerView.addSubview(giftsCoverView)
|
||||||
|
}
|
||||||
|
if additive {
|
||||||
|
transition.updateFrameAdditive(view: giftsCoverView, frame: CGRect(origin: CGPoint(x: -3.0, y: bannerFrame.height - giftsCoverSize.height - bannerInset), size: giftsCoverSize))
|
||||||
|
} else {
|
||||||
|
transition.updateFrame(view: giftsCoverView, frame: CGRect(origin: CGPoint(x: 0.0, y: bannerFrame.height - giftsCoverSize.height - bannerInset), size: giftsCoverSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if additive {
|
if additive {
|
||||||
transition.updateFrameAdditive(node: self.backgroundNode, frame: backgroundFrame)
|
transition.updateFrameAdditive(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition)
|
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition)
|
||||||
|
@ -6300,6 +6300,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) {
|
if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) {
|
||||||
|
if let cachedUserData = strongSelf.data?.cachedData as? CachedUserData, let _ = cachedUserData.sendPaidMessageStars {
|
||||||
|
|
||||||
|
} else {
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_StartSecretChat, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_StartSecretChat, icon: { theme in
|
||||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Lock"), color: theme.contextMenu.primaryColor)
|
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Lock"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
@ -6308,6 +6311,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
self?.openStartSecretChat()
|
self?.openStartSecretChat()
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if user.botInfo == nil && data.isContact, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone {
|
if user.botInfo == nil && data.isContact, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone {
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_ShareContactButton, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_ShareContactButton, icon: { theme in
|
||||||
@ -11671,7 +11675,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil)
|
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil)
|
||||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
||||||
if additive {
|
if additive {
|
||||||
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
||||||
@ -12056,7 +12060,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil)
|
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, profileGiftsContext: self.data?.profileGiftsContext, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||||
@ -13691,7 +13695,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.savedMessagesPeer ?? self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, peerNotificationSettings: self.screenNode.data?.peerNotificationSettings, threadNotificationSettings: self.screenNode.data?.threadNotificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false, animateHeader: false)
|
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.savedMessagesPeer ?? self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, peerNotificationSettings: self.screenNode.data?.peerNotificationSettings, threadNotificationSettings: self.screenNode.data?.threadNotificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, profileGiftsContext: self.screenNode.data?.profileGiftsContext, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false, animateHeader: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height
|
let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height
|
||||||
|
@ -455,7 +455,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
} else {
|
} else {
|
||||||
switch transaction.peer {
|
switch transaction.peer {
|
||||||
case let .peer(peer):
|
case let .peer(peer):
|
||||||
if transaction.flags.contains(.isPaidMessage) {
|
if let months = transaction.premiumGiftMonths {
|
||||||
|
titleText = strings.Stars_Transaction_TelegramPremium(months)
|
||||||
|
} else if transaction.flags.contains(.isPaidMessage) {
|
||||||
isPaidMessage = true
|
isPaidMessage = true
|
||||||
titleText = strings.Stars_Transaction_PaidMessage(transaction.paidMessageCount ?? 1)
|
titleText = strings.Stars_Transaction_PaidMessage(transaction.paidMessageCount ?? 1)
|
||||||
} else if !transaction.media.isEmpty {
|
} else if !transaction.media.isEmpty {
|
||||||
@ -474,8 +476,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
via = strings.Stars_Transaction_PremiumBotTopUp_Subtitle
|
via = strings.Stars_Transaction_PremiumBotTopUp_Subtitle
|
||||||
case .fragment:
|
case .fragment:
|
||||||
if parentPeer.id == component.context.account.peerId {
|
if parentPeer.id == component.context.account.peerId {
|
||||||
|
if (transaction.count.value < 0 && !transaction.flags.contains(.isRefund)) || (transaction.count.value > 0 && transaction.flags.contains(.isRefund)) {
|
||||||
|
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
|
||||||
|
via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
|
||||||
|
} else {
|
||||||
titleText = strings.Stars_Transaction_FragmentTopUp_Title
|
titleText = strings.Stars_Transaction_FragmentTopUp_Title
|
||||||
via = strings.Stars_Transaction_FragmentTopUp_Subtitle
|
via = strings.Stars_Transaction_FragmentTopUp_Subtitle
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
|
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
|
||||||
via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
|
via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
|
||||||
|
@ -304,7 +304,10 @@ final class StarsTransactionsListPanelComponent: Component {
|
|||||||
var uniqueGift: StarGift.UniqueGift?
|
var uniqueGift: StarGift.UniqueGift?
|
||||||
switch item.peer {
|
switch item.peer {
|
||||||
case let .peer(peer):
|
case let .peer(peer):
|
||||||
if item.flags.contains(.isPaidMessage) {
|
if let months = item.premiumGiftMonths {
|
||||||
|
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||||
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramPremium(months)
|
||||||
|
} else if item.flags.contains(.isPaidMessage) {
|
||||||
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||||
itemSubtitle = environment.strings.Stars_Intro_Transaction_PaidMessage(item.paidMessageCount ?? 1)
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_PaidMessage(item.paidMessageCount ?? 1)
|
||||||
} else if let starGift = item.starGift {
|
} else if let starGift = item.starGift {
|
||||||
@ -352,10 +355,15 @@ final class StarsTransactionsListPanelComponent: Component {
|
|||||||
itemTitle = environment.strings.Stars_Intro_Transaction_Gift_UnknownUser
|
itemTitle = environment.strings.Stars_Intro_Transaction_Gift_UnknownUser
|
||||||
itemSubtitle = environment.strings.Stars_Intro_Transaction_Gift_Title
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_Gift_Title
|
||||||
itemPeer = .fragment
|
itemPeer = .fragment
|
||||||
|
} else {
|
||||||
|
if (item.count.value < 0 && !item.flags.contains(.isRefund)) || (item.count.value > 0 && item.flags.contains(.isRefund)) {
|
||||||
|
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentWithdrawal_Title
|
||||||
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentWithdrawal_Subtitle
|
||||||
} else {
|
} else {
|
||||||
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title
|
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title
|
||||||
itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Subtitle
|
itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Subtitle
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if item.count > StarsAmount.zero && !item.flags.contains(.isRefund) {
|
if item.count > StarsAmount.zero && !item.flags.contains(.isRefund) {
|
||||||
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title
|
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title
|
||||||
|
@ -20,7 +20,7 @@ extension ChatControllerImpl {
|
|||||||
completion(false)
|
completion(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars {
|
if let sendPaidMessageStars = self.presentationInterfaceState.sendPaidMessageStars, self.presentationInterfaceState.interfaceState.editMessage == nil {
|
||||||
let totalAmount = sendPaidMessageStars.value * Int64(count)
|
let totalAmount = sendPaidMessageStars.value * Int64(count)
|
||||||
|
|
||||||
let _ = (ApplicationSpecificNotice.dismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id)
|
let _ = (ApplicationSpecificNotice.dismissedPaidMessageWarningNamespace(accountManager: self.context.sharedContext.accountManager, peerId: peer.id)
|
||||||
|
@ -2067,11 +2067,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, sendCurrentMessage: { [weak self] silentPosting, messageEffect in
|
}, sendCurrentMessage: { [weak self] silentPosting, messageEffect in
|
||||||
if let strongSelf = self {
|
if let self {
|
||||||
if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState {
|
if let _ = self.presentationInterfaceState.interfaceState.mediaDraftState {
|
||||||
strongSelf.sendMediaRecording(silentPosting: silentPosting, messageEffect: messageEffect)
|
self.sendMediaRecording(silentPosting: silentPosting, messageEffect: messageEffect)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, messageEffect: messageEffect)
|
self.presentPaidMessageAlertIfNeeded(count: 1, completion: { [weak self] postpone in
|
||||||
|
if let self {
|
||||||
|
self.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, postpone: postpone, messageEffect: messageEffect)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, sendMessage: { [weak self] text in
|
}, sendMessage: { [weak self] text in
|
||||||
|
@ -272,7 +272,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
|||||||
self.validLayout = size
|
self.validLayout = size
|
||||||
|
|
||||||
var innerSize = size
|
var innerSize = size
|
||||||
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars {
|
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.interfaceState.editMessage == nil {
|
||||||
self.sendButton.imageNode.alpha = 0.0
|
self.sendButton.imageNode.alpha = 0.0
|
||||||
self.textNode.isHidden = false
|
self.textNode.isHidden = false
|
||||||
|
|
||||||
|
@ -1915,7 +1915,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder
|
placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder
|
||||||
} else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation, !replyThreadMessage.isForumPost, replyThreadMessage.peerId != self.context?.account.peerId {
|
} else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation, !replyThreadMessage.isForumPost, replyThreadMessage.peerId != self.context?.account.peerId {
|
||||||
if replyThreadMessage.isChannelPost {
|
if replyThreadMessage.isChannelPost {
|
||||||
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars {
|
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.interfaceState.editMessage == nil {
|
||||||
placeholder = interfaceState.strings.Chat_InputTextPaidCommentPlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))").string
|
placeholder = interfaceState.strings.Chat_InputTextPaidCommentPlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))").string
|
||||||
placeholderHasStar = true
|
placeholderHasStar = true
|
||||||
} else {
|
} else {
|
||||||
@ -1931,7 +1931,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
placeholder = interfaceState.strings.Chat_InputPlaceholderMessageInTopic(forumTopicData.title).string
|
placeholder = interfaceState.strings.Chat_InputPlaceholderMessageInTopic(forumTopicData.title).string
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars {
|
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.interfaceState.editMessage == nil {
|
||||||
placeholder = interfaceState.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))").string
|
placeholder = interfaceState.strings.Chat_InputTextPaidMessagePlaceholder(" # \(presentationStringsFormattedNumber(Int32(sendPaidMessageStars.value), interfaceState.dateTimeFormat.groupingSeparator))").string
|
||||||
placeholderHasStar = true
|
placeholderHasStar = true
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user