mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'beta' of gitlab.com:peter-iakovlev/telegram-ios into beta
This commit is contained in:
commit
f9f58e1ca7
@ -13573,6 +13573,7 @@ Sorry for the inconvenience.";
|
|||||||
"FolderLinkPreview.ToastFolderAddedTitleV2" = "Folder {folder} Added";
|
"FolderLinkPreview.ToastFolderAddedTitleV2" = "Folder {folder} Added";
|
||||||
|
|
||||||
"Stars.Purchase.UpgradeStarGiftInfo" = "Buy Stars to upgrade your gift into a unique collectible.";
|
"Stars.Purchase.UpgradeStarGiftInfo" = "Buy Stars to upgrade your gift into a unique collectible.";
|
||||||
|
"Stars.Purchase.TransferStarGiftInfo" = "Buy Stars to transfer ownership of your unique collectible.";
|
||||||
|
|
||||||
"Gift.Send.Upgrade" = "Make Unique for %@";
|
"Gift.Send.Upgrade" = "Make Unique for %@";
|
||||||
"Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()";
|
"Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()";
|
||||||
|
@ -136,6 +136,7 @@ public enum StarsPurchasePurpose: Equatable {
|
|||||||
case unlockMedia(requiredStars: Int64)
|
case unlockMedia(requiredStars: Int64)
|
||||||
case starGift(peerId: EnginePeer.Id, requiredStars: Int64)
|
case starGift(peerId: EnginePeer.Id, requiredStars: Int64)
|
||||||
case upgradeStarGift(requiredStars: Int64)
|
case upgradeStarGift(requiredStars: Int64)
|
||||||
|
case transferStarGift(requiredStars: Int64)
|
||||||
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
|
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4098,7 +4098,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.secretIconNode = iconNode
|
strongSelf.secretIconNode = iconNode
|
||||||
}
|
}
|
||||||
iconNode.image = currentSecretIconImage
|
iconNode.image = currentSecretIconImage
|
||||||
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size))
|
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleLeftOffset, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size))
|
||||||
titleOffset += currentSecretIconImage.size.width + 3.0
|
titleOffset += currentSecretIconImage.size.width + 3.0
|
||||||
} else if let secretIconNode = strongSelf.secretIconNode {
|
} else if let secretIconNode = strongSelf.secretIconNode {
|
||||||
strongSelf.secretIconNode = nil
|
strongSelf.secretIconNode = nil
|
||||||
|
@ -591,7 +591,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
|||||||
allSelected = false
|
allSelected = false
|
||||||
}
|
}
|
||||||
var actionTitle: String?
|
var actionTitle: String?
|
||||||
if peerIds.count > 1 {
|
if !"".isEmpty, peerIds.count > 1 {
|
||||||
actionTitle = allSelected ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : strings.Premium_Gift_ContactSelection_SelectAll.uppercased()
|
actionTitle = allSelected ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : strings.Premium_Gift_ContactSelection_SelectAll.uppercased()
|
||||||
}
|
}
|
||||||
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(title.uppercased(), AnyHashable(10 * sectionId + (allSelected ? 1 : 0))), theme: theme, strings: strings, actionTitle: actionTitle, action: { _ in
|
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(title.uppercased(), AnyHashable(10 * sectionId + (allSelected ? 1 : 0))), theme: theme, strings: strings, actionTitle: actionTitle, action: { _ in
|
||||||
|
@ -129,7 +129,7 @@ public final class TextAlertContentActionNode: HighlightableButtonNode {
|
|||||||
if let range = attributedString.string.range(of: "$") {
|
if let range = attributedString.string.range(of: "$") {
|
||||||
attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string))
|
attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string))
|
||||||
attributedString.addAttribute(.foregroundColor, value: color, range: NSRange(range, in: attributedString.string))
|
attributedString.addAttribute(.foregroundColor, value: color, range: NSRange(range, in: attributedString.string))
|
||||||
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
|
attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setAttributedTitle(attributedString, for: [])
|
self.setAttributedTitle(attributedString, for: [])
|
||||||
|
@ -231,6 +231,8 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
|
|
||||||
private let disposableSet = DisposableDict<String>()
|
private let disposableSet = DisposableDict<String>()
|
||||||
|
|
||||||
|
private var lastRequestTimestamp: Double?
|
||||||
|
|
||||||
public init(engine: TelegramEngine) {
|
public init(engine: TelegramEngine) {
|
||||||
self.engine = engine
|
self.engine = engine
|
||||||
|
|
||||||
@ -255,12 +257,16 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
productRequest.start()
|
productRequest.start()
|
||||||
|
|
||||||
self.productRequest = productRequest
|
self.productRequest = productRequest
|
||||||
|
self.lastRequestTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
}
|
}
|
||||||
|
|
||||||
public var availableProducts: Signal<[Product], NoError> {
|
public var availableProducts: Signal<[Product], NoError> {
|
||||||
if self.products.isEmpty && self.productRequest == nil {
|
if self.products.isEmpty {
|
||||||
|
if let lastRequestTimestamp, CFAbsoluteTimeGetCurrent() - lastRequestTimestamp > 10.0 {
|
||||||
|
Logger.shared.log("InAppPurchaseManager", "No available products, rerequest")
|
||||||
self.requestProducts()
|
self.requestProducts()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return self.productsPromise.get()
|
return self.productsPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,9 +67,7 @@
|
|||||||
- (SSignal *)coverImageSignalForItem:(NSObject<TGMediaEditableItem> *)item;
|
- (SSignal *)coverImageSignalForItem:(NSObject<TGMediaEditableItem> *)item;
|
||||||
- (void)setCoverImage:(UIImage *)image position:(NSNumber *)position forItem:(id<TGMediaEditableItem>)item;
|
- (void)setCoverImage:(UIImage *)image position:(NSNumber *)position forItem:(id<TGMediaEditableItem>)item;
|
||||||
- (UIImage *)coverImageForItem:(NSObject<TGMediaEditableItem> *)item;
|
- (UIImage *)coverImageForItem:(NSObject<TGMediaEditableItem> *)item;
|
||||||
|
|
||||||
- (NSNumber *)coverPositionForItem:(NSObject<TGMediaEditableItem> *)item;
|
- (NSNumber *)coverPositionForItem:(NSObject<TGMediaEditableItem> *)item;
|
||||||
- (void)setCoverImage:(UIImage *)image position:(NSNumber *)position forItem:(id<TGMediaEditableItem>)item;
|
|
||||||
|
|
||||||
- (void)setTemporaryRep:(id)rep forItem:(id<TGMediaEditableItem>)item;
|
- (void)setTemporaryRep:(id)rep forItem:(id<TGMediaEditableItem>)item;
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ class EmojiHeaderComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
weak var animateFrom: UIView?
|
weak var animateFrom: UIView?
|
||||||
|
var sourceRect: CGRect?
|
||||||
weak var containerView: UIView?
|
weak var containerView: UIView?
|
||||||
|
|
||||||
let statusView: ComponentHostView<Empty>
|
let statusView: ComponentHostView<Empty>
|
||||||
@ -116,7 +117,12 @@ class EmojiHeaderComponent: Component {
|
|||||||
|
|
||||||
let initialPosition = self.statusView.center
|
let initialPosition = self.statusView.center
|
||||||
let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView)
|
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: 0.0)
|
|
||||||
|
var sourceOffset: CGPoint = .zero
|
||||||
|
if let sourceRect = self.sourceRect {
|
||||||
|
sourceOffset = CGPoint(x: sourceRect.center.x - animateFrom.frame.width / 2.0, y: 0.0)
|
||||||
|
}
|
||||||
|
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: sourceOffset.x, dy: sourceOffset.y)
|
||||||
|
|
||||||
containerView.addSubview(self.statusView)
|
containerView.addSubview(self.statusView)
|
||||||
self.statusView.center = targetPosition
|
self.statusView.center = targetPosition
|
||||||
@ -127,6 +133,7 @@ class EmojiHeaderComponent: Component {
|
|||||||
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring)
|
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
Queue.mainQueue().after(0.55, {
|
Queue.mainQueue().after(0.55, {
|
||||||
|
self.statusView.layer.removeAllAnimations()
|
||||||
self.addSubview(self.statusView)
|
self.addSubview(self.statusView)
|
||||||
self.statusView.center = initialPosition
|
self.statusView.center = initialPosition
|
||||||
})
|
})
|
||||||
|
@ -3706,6 +3706,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public weak var sourceView: UIView?
|
public weak var sourceView: UIView?
|
||||||
|
public var sourceRect: CGRect?
|
||||||
public weak var containerView: UIView?
|
public weak var containerView: UIView?
|
||||||
public var animationColor: UIColor?
|
public var animationColor: UIColor?
|
||||||
|
|
||||||
@ -3884,6 +3885,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
if let sourceView = self.sourceView {
|
if let sourceView = self.sourceView {
|
||||||
view.animateFrom = sourceView
|
view.animateFrom = sourceView
|
||||||
|
view.sourceRect = self.sourceRect
|
||||||
view.containerView = self.containerView
|
view.containerView = self.containerView
|
||||||
|
|
||||||
view.animateIn()
|
view.animateIn()
|
||||||
|
@ -315,7 +315,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati
|
|||||||
entries.append(.footerItem(footer))
|
entries.append(.footerItem(footer))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !peers.isEmpty {
|
if !peers.isEmpty || state.enableForPremium || state.enableForBots {
|
||||||
entries.append(.deleteItem(presentationData.strings.Privacy_Exceptions_DeleteAllExceptions))
|
entries.append(.deleteItem(presentationData.strings.Privacy_Exceptions_DeleteAllExceptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,14 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network,
|
|||||||
return .single([])
|
return .single([])
|
||||||
}
|
}
|
||||||
|
|
||||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
if let channel = peer as? TelegramChannel {
|
||||||
|
if case .group = channel.info {
|
||||||
|
|
||||||
|
} else if channel.adminRights != nil || channel.flags.contains(.isCreator) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return .single([])
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single([])
|
return .single([])
|
||||||
}
|
}
|
||||||
|
@ -395,7 +395,7 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv
|
|||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
var message: Api.TextWithEntities?
|
var message: Api.TextWithEntities?
|
||||||
if let text, !text.isEmpty {
|
if let text, !text.isEmpty {
|
||||||
flags |= (1 << 1)
|
flags |= (1 << 0)
|
||||||
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
|
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
|
||||||
}
|
}
|
||||||
return .inputInvoicePremiumGiftStars(flags: flags, userId: inputUser, months: option.months, message: message)
|
return .inputInvoicePremiumGiftStars(flags: flags, userId: inputUser, months: option.months, message: message)
|
||||||
|
@ -568,6 +568,21 @@ private final class StarsContextImpl {
|
|||||||
self._state = state
|
self._state = state
|
||||||
self._statePromise.set(.single(state))
|
self._statePromise.set(.single(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onUpdate: Signal<Void, NoError> {
|
||||||
|
return self._statePromise.get()
|
||||||
|
|> take(until: { value in
|
||||||
|
if let value {
|
||||||
|
if !value.flags.contains(.isPendingBalance) {
|
||||||
|
return SignalTakeAction(passthrough: true, complete: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SignalTakeAction(passthrough: false, complete: false)
|
||||||
|
})
|
||||||
|
|> map { _ in
|
||||||
|
return Void()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StarsContext.State.Transaction {
|
private extension StarsContext.State.Transaction {
|
||||||
@ -1011,6 +1026,17 @@ public final class StarsContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var onUpdate: Signal<Void, NoError> {
|
||||||
|
return Signal { subscriber in
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
self.impl.with { impl in
|
||||||
|
disposable.set(impl.onUpdate.start(next: { value in
|
||||||
|
subscriber.putNext(value)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return disposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(account: Account) {
|
init(account: Account) {
|
||||||
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
|
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
|
||||||
|
@ -170,6 +170,12 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
public let outgoingDateAndStatusRepliesIcon: UIImage
|
public let outgoingDateAndStatusRepliesIcon: UIImage
|
||||||
public let mediaRepliesIcon: UIImage
|
public let mediaRepliesIcon: UIImage
|
||||||
public let freeRepliesIcon: UIImage
|
public let freeRepliesIcon: UIImage
|
||||||
|
|
||||||
|
public let incomingDateAndStatusStarsIcon: UIImage
|
||||||
|
public let outgoingDateAndStatusStarsIcon: UIImage
|
||||||
|
public let mediaStarsIcon: UIImage
|
||||||
|
public let freeStarsIcon: UIImage
|
||||||
|
|
||||||
public let incomingDateAndStatusPinnedIcon: UIImage
|
public let incomingDateAndStatusPinnedIcon: UIImage
|
||||||
public let outgoingDateAndStatusPinnedIcon: UIImage
|
public let outgoingDateAndStatusPinnedIcon: UIImage
|
||||||
public let mediaPinnedIcon: UIImage
|
public let mediaPinnedIcon: UIImage
|
||||||
@ -358,6 +364,12 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
||||||
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
||||||
|
|
||||||
|
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
|
||||||
|
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
|
||||||
|
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||||
|
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
|
||||||
|
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
|
||||||
|
|
||||||
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
||||||
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
||||||
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||||
@ -479,6 +491,12 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
|
||||||
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
|
||||||
|
|
||||||
|
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
|
||||||
|
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
|
||||||
|
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||||
|
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
|
||||||
|
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
|
||||||
|
|
||||||
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
||||||
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
||||||
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||||
|
@ -81,7 +81,7 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
|
|||||||
|
|
||||||
public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
|
public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
|
||||||
var balanceText = presentationStringsFormattedNumber(Int32(amount.value), dateTimeFormat.groupingSeparator)
|
var balanceText = presentationStringsFormattedNumber(Int32(amount.value), dateTimeFormat.groupingSeparator)
|
||||||
let fraction = Double(amount.nanos) / 10e6
|
let fraction = abs(Double(amount.nanos)) / 10e6
|
||||||
if fraction > 0.0 {
|
if fraction > 0.0 {
|
||||||
balanceText.append(dateTimeFormat.decimalSeparator)
|
balanceText.append(dateTimeFormat.decimalSeparator)
|
||||||
balanceText.append("\(Int32(fraction))")
|
balanceText.append("\(Int32(fraction))")
|
||||||
|
@ -1213,9 +1213,9 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
|
|||||||
private var currentTheme: PresentationTheme?
|
private var currentTheme: PresentationTheme?
|
||||||
private var currentStrings: PresentationStrings?
|
private var currentStrings: PresentationStrings?
|
||||||
|
|
||||||
private let stars: StarsAmount?
|
private let stars: Int64?
|
||||||
|
|
||||||
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: StarsAmount?) {
|
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: Int64?) {
|
||||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||||
self.stars = stars
|
self.stars = stars
|
||||||
@ -1295,7 +1295,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if let amount = self.stars {
|
if let amount = self.stars {
|
||||||
let starsString = presentationStringsFormattedNumber(Int32(amount.value), interfaceState.dateTimeFormat.groupingSeparator)
|
let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator)
|
||||||
let rawText: String
|
let rawText: String
|
||||||
if self.isPremiumDisabled {
|
if self.isPremiumDisabled {
|
||||||
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
|
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
|
||||||
@ -1426,7 +1426,7 @@ private enum ChatEmptyNodeContentType: Equatable {
|
|||||||
case greeting
|
case greeting
|
||||||
case topic
|
case topic
|
||||||
case premiumRequired
|
case premiumRequired
|
||||||
case starsRequired
|
case starsRequired(Int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
|
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
|
||||||
@ -1815,8 +1815,8 @@ public final class ChatEmptyNode: ASDisplayNode {
|
|||||||
} else if let _ = interfaceState.peerNearbyData {
|
} else if let _ = interfaceState.peerNearbyData {
|
||||||
contentType = .peerNearby
|
contentType = .peerNearby
|
||||||
} else if let peer = peer as? TelegramUser {
|
} else if let peer = peer as? TelegramUser {
|
||||||
if let _ = interfaceState.sendPaidMessageStars {
|
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil {
|
||||||
contentType = .starsRequired
|
contentType = .starsRequired(sendPaidMessageStars.value)
|
||||||
} else if interfaceState.isPremiumRequiredForMessaging {
|
} else if interfaceState.isPremiumRequiredForMessaging {
|
||||||
contentType = .premiumRequired
|
contentType = .premiumRequired
|
||||||
} else {
|
} else {
|
||||||
@ -1881,8 +1881,8 @@ public final class ChatEmptyNode: ASDisplayNode {
|
|||||||
node = ChatEmptyNodeTopicChatContent(context: self.context)
|
node = ChatEmptyNodeTopicChatContent(context: self.context)
|
||||||
case .premiumRequired:
|
case .premiumRequired:
|
||||||
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil)
|
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil)
|
||||||
case .starsRequired:
|
case let .starsRequired(stars):
|
||||||
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: interfaceState.sendPaidMessageStars)
|
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
|
||||||
}
|
}
|
||||||
self.content = (contentType, node)
|
self.content = (contentType, node)
|
||||||
self.addSubnode(node)
|
self.addSubnode(node)
|
||||||
@ -1893,7 +1893,12 @@ public final class ChatEmptyNode: ASDisplayNode {
|
|||||||
node.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction)
|
node.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.isUserInteractionEnabled = [.peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud].contains(contentType)
|
switch contentType {
|
||||||
|
case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud:
|
||||||
|
self.isUserInteractionEnabled = true
|
||||||
|
default:
|
||||||
|
self.isUserInteractionEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
|
let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
|
||||||
|
|
||||||
|
@ -1044,6 +1044,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var edited = false
|
var edited = false
|
||||||
var viewCount: Int? = nil
|
var viewCount: Int? = nil
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -1057,6 +1058,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1086,6 +1089,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: messageEffect,
|
messageEffect: messageEffect,
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.message),
|
canViewReactionList: canViewMessageReactionList(message: item.message),
|
||||||
|
@ -675,6 +675,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: context.account.peerId, accountPeer: associatedData.accountPeer, message: message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: context.account.peerId, accountPeer: associatedData.accountPeer, message: message)
|
||||||
if message.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) || presentationData.isPreview {
|
if message.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) || presentationData.isPreview {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -688,6 +689,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,6 +750,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
|
areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
|
||||||
messageEffect: message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects),
|
messageEffect: message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: message.isSelfExpiring,
|
hasAutoremove: message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: message),
|
canViewReactionList: canViewMessageReactionList(message: message),
|
||||||
|
@ -130,7 +130,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
needReactions = false
|
needReactions = false
|
||||||
break outer
|
break outer
|
||||||
} else if let _ = attribute as? PaidStarsMessageAttribute, !addedPriceInfo {
|
} else if let _ = attribute as? PaidStarsMessageAttribute, !addedPriceInfo, message.id.peerId.namespace == Namespaces.Peer.CloudUser {
|
||||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
addedPriceInfo = true
|
addedPriceInfo = true
|
||||||
}
|
}
|
||||||
@ -299,7 +299,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isMediaInverted {
|
if isMediaInverted {
|
||||||
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: 0)
|
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: addedPriceInfo ? 1 : 0)
|
||||||
} else {
|
} else {
|
||||||
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
|
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
|
||||||
needReactions = false
|
needReactions = false
|
||||||
@ -327,7 +327,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute, attribute.leadingPreview {
|
if let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute, attribute.leadingPreview {
|
||||||
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0)
|
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: addedPriceInfo ? 1 : 0)
|
||||||
} else {
|
} else {
|
||||||
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
}
|
}
|
||||||
@ -362,7 +362,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
if result.isEmpty {
|
if result.isEmpty {
|
||||||
needReactions = false
|
needReactions = false
|
||||||
}
|
}
|
||||||
result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0)
|
result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: addedPriceInfo ? 1 : 0)
|
||||||
} else {
|
} else {
|
||||||
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
needReactions = false
|
needReactions = false
|
||||||
@ -2276,6 +2276,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: message)
|
||||||
if message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -2289,6 +2290,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2337,6 +2340,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: message.isSelfExpiring,
|
hasAutoremove: message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: message),
|
canViewReactionList: canViewMessageReactionList(message: message),
|
||||||
|
@ -233,6 +233,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -246,6 +247,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,6 +303,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: messageEffect,
|
messageEffect: messageEffect,
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -195,6 +195,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
var areReactionsTags: Bool
|
var areReactionsTags: Bool
|
||||||
var messageEffect: AvailableMessageEffects.MessageEffect?
|
var messageEffect: AvailableMessageEffects.MessageEffect?
|
||||||
var replyCount: Int
|
var replyCount: Int
|
||||||
|
var starsCount: Int64?
|
||||||
var isPinned: Bool
|
var isPinned: Bool
|
||||||
var hasAutoremove: Bool
|
var hasAutoremove: Bool
|
||||||
var canViewReactionList: Bool
|
var canViewReactionList: Bool
|
||||||
@ -218,6 +219,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
areReactionsTags: Bool,
|
areReactionsTags: Bool,
|
||||||
messageEffect: AvailableMessageEffects.MessageEffect?,
|
messageEffect: AvailableMessageEffects.MessageEffect?,
|
||||||
replyCount: Int,
|
replyCount: Int,
|
||||||
|
starsCount: Int64?,
|
||||||
isPinned: Bool,
|
isPinned: Bool,
|
||||||
hasAutoremove: Bool,
|
hasAutoremove: Bool,
|
||||||
canViewReactionList: Bool,
|
canViewReactionList: Bool,
|
||||||
@ -240,6 +242,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
self.areReactionsTags = areReactionsTags
|
self.areReactionsTags = areReactionsTags
|
||||||
self.messageEffect = messageEffect
|
self.messageEffect = messageEffect
|
||||||
self.replyCount = replyCount
|
self.replyCount = replyCount
|
||||||
|
self.starsCount = starsCount
|
||||||
self.isPinned = isPinned
|
self.isPinned = isPinned
|
||||||
self.hasAutoremove = hasAutoremove
|
self.hasAutoremove = hasAutoremove
|
||||||
self.canViewReactionList = canViewReactionList
|
self.canViewReactionList = canViewReactionList
|
||||||
@ -262,6 +265,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
private var repliesIcon: ASImageNode?
|
private var repliesIcon: ASImageNode?
|
||||||
private var selfExpiringIcon: ASImageNode?
|
private var selfExpiringIcon: ASImageNode?
|
||||||
private var replyCountNode: TextNode?
|
private var replyCountNode: TextNode?
|
||||||
|
private var starsIcon: ASImageNode?
|
||||||
|
private var starsCountNode: TextNode?
|
||||||
|
|
||||||
private var type: ChatMessageDateAndStatusType?
|
private var type: ChatMessageDateAndStatusType?
|
||||||
private var theme: ChatPresentationThemeData?
|
private var theme: ChatPresentationThemeData?
|
||||||
@ -316,11 +321,13 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
var currentBackgroundNode = self.backgroundNode
|
var currentBackgroundNode = self.backgroundNode
|
||||||
var currentImpressionIcon = self.impressionIcon
|
var currentImpressionIcon = self.impressionIcon
|
||||||
var currentRepliesIcon = self.repliesIcon
|
var currentRepliesIcon = self.repliesIcon
|
||||||
|
var currentStarsIcon = self.starsIcon
|
||||||
|
|
||||||
let currentType = self.type
|
let currentType = self.type
|
||||||
let currentTheme = self.theme
|
let currentTheme = self.theme
|
||||||
|
|
||||||
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
|
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
|
||||||
|
let makeStarsCountLayout = TextNode.asyncLayout(self.starsCountNode)
|
||||||
|
|
||||||
let reactionButtonsContainer = self.reactionButtonsContainer
|
let reactionButtonsContainer = self.reactionButtonsContainer
|
||||||
|
|
||||||
@ -337,6 +344,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
let clockMinImage: UIImage?
|
let clockMinImage: UIImage?
|
||||||
var impressionImage: UIImage?
|
var impressionImage: UIImage?
|
||||||
var repliesImage: UIImage?
|
var repliesImage: UIImage?
|
||||||
|
var starsImage: UIImage?
|
||||||
|
|
||||||
let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType
|
let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType
|
||||||
|
|
||||||
@ -404,6 +412,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if arguments.isPinned {
|
} else if arguments.isPinned {
|
||||||
repliesImage = graphics.incomingDateAndStatusPinnedIcon
|
repliesImage = graphics.incomingDateAndStatusPinnedIcon
|
||||||
}
|
}
|
||||||
|
if (arguments.starsCount ?? 0) != 0 {
|
||||||
|
starsImage = graphics.incomingDateAndStatusStarsIcon
|
||||||
|
}
|
||||||
case let .BubbleOutgoing(status):
|
case let .BubbleOutgoing(status):
|
||||||
dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||||
outgoingStatus = status
|
outgoingStatus = status
|
||||||
@ -420,6 +431,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if arguments.isPinned {
|
} else if arguments.isPinned {
|
||||||
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
|
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
|
||||||
}
|
}
|
||||||
|
if (arguments.starsCount ?? 0) != 0 {
|
||||||
|
starsImage = graphics.outgoingDateAndStatusStarsIcon
|
||||||
|
}
|
||||||
case .ImageIncoming:
|
case .ImageIncoming:
|
||||||
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
||||||
backgroundImage = graphics.dateAndStatusMediaBackground
|
backgroundImage = graphics.dateAndStatusMediaBackground
|
||||||
@ -436,6 +450,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if arguments.isPinned {
|
} else if arguments.isPinned {
|
||||||
repliesImage = graphics.mediaPinnedIcon
|
repliesImage = graphics.mediaPinnedIcon
|
||||||
}
|
}
|
||||||
|
if (arguments.starsCount ?? 0) != 0 {
|
||||||
|
starsImage = graphics.mediaStarsIcon
|
||||||
|
}
|
||||||
case let .ImageOutgoing(status):
|
case let .ImageOutgoing(status):
|
||||||
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
|
||||||
outgoingStatus = status
|
outgoingStatus = status
|
||||||
@ -453,6 +470,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if arguments.isPinned {
|
} else if arguments.isPinned {
|
||||||
repliesImage = graphics.mediaPinnedIcon
|
repliesImage = graphics.mediaPinnedIcon
|
||||||
}
|
}
|
||||||
|
if (arguments.starsCount ?? 0) != 0 {
|
||||||
|
starsImage = graphics.mediaStarsIcon
|
||||||
|
}
|
||||||
case .FreeIncoming:
|
case .FreeIncoming:
|
||||||
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
|
||||||
dateColor = serviceColor.primaryText
|
dateColor = serviceColor.primaryText
|
||||||
@ -471,6 +491,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if arguments.isPinned {
|
} else if arguments.isPinned {
|
||||||
repliesImage = graphics.freePinnedIcon
|
repliesImage = graphics.freePinnedIcon
|
||||||
}
|
}
|
||||||
|
if (arguments.starsCount ?? 0) != 0 {
|
||||||
|
starsImage = graphics.freeStarsIcon
|
||||||
|
}
|
||||||
case let .FreeOutgoing(status):
|
case let .FreeOutgoing(status):
|
||||||
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
|
||||||
dateColor = serviceColor.primaryText
|
dateColor = serviceColor.primaryText
|
||||||
@ -489,6 +512,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
} else if arguments.isPinned {
|
} else if arguments.isPinned {
|
||||||
repliesImage = graphics.freePinnedIcon
|
repliesImage = graphics.freePinnedIcon
|
||||||
}
|
}
|
||||||
|
if (arguments.starsCount ?? 0) != 0 {
|
||||||
|
starsImage = graphics.freeStarsIcon
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedDateText = arguments.dateText
|
var updatedDateText = arguments.dateText
|
||||||
@ -541,6 +567,20 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
currentRepliesIcon = nil
|
currentRepliesIcon = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var starsIconSize = CGSize()
|
||||||
|
if let starsImage = starsImage {
|
||||||
|
if currentStarsIcon == nil {
|
||||||
|
let iconNode = ASImageNode()
|
||||||
|
iconNode.isLayerBacked = true
|
||||||
|
iconNode.displayWithoutProcessing = true
|
||||||
|
iconNode.displaysAsynchronously = false
|
||||||
|
currentStarsIcon = iconNode
|
||||||
|
}
|
||||||
|
starsIconSize = starsImage.size
|
||||||
|
} else {
|
||||||
|
currentStarsIcon = nil
|
||||||
|
}
|
||||||
|
|
||||||
if let outgoingStatus = outgoingStatus {
|
if let outgoingStatus = outgoingStatus {
|
||||||
switch outgoingStatus {
|
switch outgoingStatus {
|
||||||
case .Sending:
|
case .Sending:
|
||||||
@ -652,6 +692,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||||
|
var starsCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||||
|
|
||||||
let reactionSize: CGFloat = 8.0
|
let reactionSize: CGFloat = 8.0
|
||||||
let reactionSpacing: CGFloat = 2.0
|
let reactionSpacing: CGFloat = 2.0
|
||||||
@ -671,11 +712,29 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
|
|
||||||
let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
|
let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
|
||||||
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
|
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
|
||||||
|
if arguments.starsCount != nil {
|
||||||
|
reactionInset += 3.0
|
||||||
|
}
|
||||||
replyCountLayoutAndApply = layoutAndApply
|
replyCountLayoutAndApply = layoutAndApply
|
||||||
} else if arguments.isPinned {
|
} else if arguments.isPinned {
|
||||||
reactionInset += 12.0
|
reactionInset += 12.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let starsCount = arguments.starsCount, starsCount > 0 {
|
||||||
|
let countString: String
|
||||||
|
if starsCount > 1000000 {
|
||||||
|
countString = "\(starsCount / 1000000)M"
|
||||||
|
} else if starsCount > 1000 {
|
||||||
|
countString = "\(starsCount / 1000)K"
|
||||||
|
} else {
|
||||||
|
countString = "\(starsCount)"
|
||||||
|
}
|
||||||
|
|
||||||
|
let layoutAndApply = makeStarsCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
|
||||||
|
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
|
||||||
|
starsCountLayoutAndApply = layoutAndApply
|
||||||
|
}
|
||||||
|
|
||||||
if arguments.messageEffect != nil {
|
if arguments.messageEffect != nil {
|
||||||
reactionInset += 13.0
|
reactionInset += 13.0
|
||||||
}
|
}
|
||||||
@ -1227,6 +1286,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
let replyCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
let replyCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
||||||
animation.animator.updateFrame(layer: node.layer, frame: replyCountFrame, completion: nil)
|
animation.animator.updateFrame(layer: node.layer, frame: replyCountFrame, completion: nil)
|
||||||
reactionOffset += 4.0 + layout.size.width
|
reactionOffset += 4.0 + layout.size.width
|
||||||
|
if currentStarsIcon != nil {
|
||||||
|
reactionOffset += 8.0
|
||||||
|
}
|
||||||
} else if let replyCountNode = strongSelf.replyCountNode {
|
} else if let replyCountNode = strongSelf.replyCountNode {
|
||||||
strongSelf.replyCountNode = nil
|
strongSelf.replyCountNode = nil
|
||||||
if animation.isAnimated {
|
if animation.isAnimated {
|
||||||
@ -1237,6 +1299,56 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
|||||||
replyCountNode.removeFromSupernode()
|
replyCountNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let currentStarsIcon = currentStarsIcon {
|
||||||
|
currentStarsIcon.displaysAsynchronously = false
|
||||||
|
if currentStarsIcon.image !== starsImage {
|
||||||
|
currentStarsIcon.image = starsImage
|
||||||
|
}
|
||||||
|
if currentStarsIcon.supernode == nil {
|
||||||
|
strongSelf.starsIcon = currentStarsIcon
|
||||||
|
strongSelf.addSubnode(currentStarsIcon)
|
||||||
|
if animation.isAnimated {
|
||||||
|
currentStarsIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let starsIconFrame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + verticalInset + floor((date.size.height - starsIconSize.height) / 2.0)), size: starsIconSize)
|
||||||
|
animation.animator.updateFrame(layer: currentStarsIcon.layer, frame: starsIconFrame, completion: nil)
|
||||||
|
reactionOffset += 9.0
|
||||||
|
} else if let starsIcon = strongSelf.starsIcon {
|
||||||
|
strongSelf.starsIcon = nil
|
||||||
|
if animation.isAnimated {
|
||||||
|
starsIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsIcon] _ in
|
||||||
|
starsIcon?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
starsIcon.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (layout, apply) = starsCountLayoutAndApply {
|
||||||
|
let node = apply()
|
||||||
|
if strongSelf.starsCountNode !== node {
|
||||||
|
strongSelf.starsCountNode?.removeFromSupernode()
|
||||||
|
strongSelf.addSubnode(node)
|
||||||
|
strongSelf.starsCountNode = node
|
||||||
|
if animation.isAnimated {
|
||||||
|
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let starsCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
||||||
|
animation.animator.updateFrame(layer: node.layer, frame: starsCountFrame, completion: nil)
|
||||||
|
reactionOffset += 4.0 + layout.size.width
|
||||||
|
} else if let starsCountNode = strongSelf.starsCountNode {
|
||||||
|
strongSelf.starsCountNode = nil
|
||||||
|
if animation.isAnimated {
|
||||||
|
starsCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsCountNode] _ in
|
||||||
|
starsCountNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
starsCountNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -295,6 +295,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
var rawText = ""
|
var rawText = ""
|
||||||
var rawEntities: [MessageTextEntity] = []
|
var rawEntities: [MessageTextEntity] = []
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -311,6 +312,8 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,6 +450,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -578,6 +578,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: nil,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -898,6 +898,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: arguments.context.account.peerId, accountPeer: arguments.associatedData.accountPeer, message: arguments.topMessage)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: arguments.context.account.peerId, accountPeer: arguments.associatedData.accountPeer, message: arguments.topMessage)
|
||||||
if arguments.topMessage.isRestricted(platform: "ios", contentSettings: arguments.context.currentContentSettings.with { $0 }) || arguments.presentationData.isPreview {
|
if arguments.topMessage.isRestricted(platform: "ios", contentSettings: arguments.context.currentContentSettings.with { $0 }) || arguments.presentationData.isPreview {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -911,6 +912,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
if let channel = arguments.message.peers[arguments.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = arguments.message.peers[arguments.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, arguments.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if arguments.forcedIsEdited {
|
if arguments.forcedIsEdited {
|
||||||
@ -956,6 +959,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
areReactionsTags: arguments.message.areReactionsTags(accountPeerId: arguments.context.account.peerId),
|
areReactionsTags: arguments.message.areReactionsTags(accountPeerId: arguments.context.account.peerId),
|
||||||
messageEffect: arguments.message.messageEffect(availableMessageEffects: arguments.associatedData.availableMessageEffects),
|
messageEffect: arguments.message.messageEffect(availableMessageEffects: arguments.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode,
|
isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode,
|
||||||
hasAutoremove: arguments.message.isSelfExpiring,
|
hasAutoremove: arguments.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: arguments.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: arguments.topMessage),
|
||||||
|
@ -524,6 +524,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
let sentViaBot = false
|
let sentViaBot = false
|
||||||
var viewCount: Int? = nil
|
var viewCount: Int? = nil
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -537,6 +538,8 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,6 +586,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: messageEffect,
|
messageEffect: messageEffect,
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -83,6 +83,7 @@ public struct ChatMessageDateAndStatus {
|
|||||||
public var dateReactions: [MessageReaction]
|
public var dateReactions: [MessageReaction]
|
||||||
public var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)]
|
public var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)]
|
||||||
public var dateReplies: Int
|
public var dateReplies: Int
|
||||||
|
public var starsCount: Int64?
|
||||||
public var isPinned: Bool
|
public var isPinned: Bool
|
||||||
public var dateText: String
|
public var dateText: String
|
||||||
|
|
||||||
@ -93,6 +94,7 @@ public struct ChatMessageDateAndStatus {
|
|||||||
dateReactions: [MessageReaction],
|
dateReactions: [MessageReaction],
|
||||||
dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)],
|
dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)],
|
||||||
dateReplies: Int,
|
dateReplies: Int,
|
||||||
|
starsCount: Int64?,
|
||||||
isPinned: Bool,
|
isPinned: Bool,
|
||||||
dateText: String
|
dateText: String
|
||||||
) {
|
) {
|
||||||
@ -102,6 +104,7 @@ public struct ChatMessageDateAndStatus {
|
|||||||
self.dateReactions = dateReactions
|
self.dateReactions = dateReactions
|
||||||
self.dateReactionPeers = dateReactionPeers
|
self.dateReactionPeers = dateReactionPeers
|
||||||
self.dateReplies = dateReplies
|
self.dateReplies = dateReplies
|
||||||
|
self.starsCount = starsCount
|
||||||
self.isPinned = isPinned
|
self.isPinned = isPinned
|
||||||
self.dateText = dateText
|
self.dateText = dateText
|
||||||
}
|
}
|
||||||
@ -1118,6 +1121,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||||||
areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
|
areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
|
||||||
messageEffect: messageEffect,
|
messageEffect: messageEffect,
|
||||||
replyCount: dateAndStatus.dateReplies,
|
replyCount: dateAndStatus.dateReplies,
|
||||||
|
starsCount: dateAndStatus.starsCount,
|
||||||
isPinned: dateAndStatus.isPinned,
|
isPinned: dateAndStatus.isPinned,
|
||||||
hasAutoremove: message.isSelfExpiring,
|
hasAutoremove: message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: message),
|
canViewReactionList: canViewMessageReactionList(message: message),
|
||||||
|
@ -192,6 +192,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -205,6 +206,8 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,6 +287,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -307,6 +307,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -323,6 +324,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,6 +376,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
dateReactions: dateReactionsAndPeers.reactions,
|
dateReactions: dateReactionsAndPeers.reactions,
|
||||||
dateReactionPeers: dateReactionsAndPeers.peers,
|
dateReactionPeers: dateReactionsAndPeers.peers,
|
||||||
dateReplies: dateReplies,
|
dateReplies: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
dateText: dateText
|
dateText: dateText
|
||||||
)
|
)
|
||||||
|
@ -317,17 +317,19 @@ private final class ChatMessagePaymentAlertContentNode: AlertContentNode, ASGest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ChatMessagePaymentAlertController: AlertController {
|
public class ChatMessagePaymentAlertController: AlertController {
|
||||||
private let context: AccountContext?
|
private let context: AccountContext?
|
||||||
private let presentationData: PresentationData
|
private let presentationData: PresentationData
|
||||||
private weak var parentNavigationController: NavigationController?
|
private weak var parentNavigationController: NavigationController?
|
||||||
|
private let showBalance: Bool
|
||||||
|
|
||||||
private let balance = ComponentView<Empty>()
|
private let balance = ComponentView<Empty>()
|
||||||
|
|
||||||
init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?) {
|
public init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?, showBalance: Bool = true) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.parentNavigationController = navigationController
|
self.parentNavigationController = navigationController
|
||||||
|
self.showBalance = showBalance
|
||||||
|
|
||||||
super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||||
|
|
||||||
@ -350,16 +352,16 @@ private class ChatMessagePaymentAlertController: AlertController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func dismissAnimated() {
|
public override func dismissAnimated() {
|
||||||
super.dismissAnimated()
|
super.dismissAnimated()
|
||||||
|
|
||||||
self.animateOut()
|
self.animateOut()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
if let context = self.context, let _ = self.parentNavigationController {
|
if let context = self.context, let _ = self.parentNavigationController, self.showBalance {
|
||||||
let insets = layout.insets(options: .statusBar)
|
let insets = layout.insets(options: .statusBar)
|
||||||
let balanceSize = self.balance.update(
|
let balanceSize = self.balance.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
|
@ -1054,6 +1054,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -1067,6 +1068,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1125,6 +1128,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -56,6 +56,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
|
|||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var rawText = ""
|
var rawText = ""
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -71,6 +72,8 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +143,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -602,6 +602,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
var edited = false
|
var edited = false
|
||||||
var viewCount: Int? = nil
|
var viewCount: Int? = nil
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -615,6 +616,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,6 +651,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.message),
|
canViewReactionList: canViewMessageReactionList(message: item.message),
|
||||||
|
@ -264,6 +264,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
var dateReplies = 0
|
var dateReplies = 0
|
||||||
|
var starsCount: Int64?
|
||||||
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.topMessage)
|
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.topMessage)
|
||||||
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
|
||||||
dateReactionsAndPeers = ([], [])
|
dateReactionsAndPeers = ([], [])
|
||||||
@ -278,6 +279,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
dateReplies = Int(attribute.count)
|
dateReplies = Int(attribute.count)
|
||||||
}
|
}
|
||||||
|
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
starsCount = attribute.stars.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,6 +650,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
|
||||||
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
|
||||||
replyCount: dateReplies,
|
replyCount: dateReplies,
|
||||||
|
starsCount: starsCount,
|
||||||
isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread),
|
isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread),
|
||||||
hasAutoremove: item.message.isSelfExpiring,
|
hasAutoremove: item.message.isSelfExpiring,
|
||||||
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
canViewReactionList: canViewMessageReactionList(message: item.topMessage),
|
||||||
|
@ -216,6 +216,8 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
private var starsStateDisposable: Disposable?
|
private var starsStateDisposable: Disposable?
|
||||||
private var starsState: StarsContext.State?
|
private var starsState: StarsContext.State?
|
||||||
|
|
||||||
|
private let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||||
|
|
||||||
private var component: GiftOptionsScreenComponent?
|
private var component: GiftOptionsScreenComponent?
|
||||||
private(set) weak var state: State?
|
private(set) weak var state: State?
|
||||||
private var environment: EnvironmentType?
|
private var environment: EnvironmentType?
|
||||||
@ -508,9 +510,19 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
gift: transferGift,
|
gift: transferGift,
|
||||||
peer: peer,
|
peer: peer,
|
||||||
transferStars: gift.transferStars ?? 0,
|
transferStars: gift.transferStars ?? 0,
|
||||||
commit: { [weak controller] in
|
navigationController: controller.navigationController as? NavigationController,
|
||||||
|
commit: { [weak self, weak controller] in
|
||||||
|
let proceed: (Bool) -> Void = { waitForTopUp in
|
||||||
|
if waitForTopUp, let starsContext = context.starsContext {
|
||||||
|
let _ = (starsContext.onUpdate
|
||||||
|
|> deliverOnMainQueue).start(next: {
|
||||||
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|
||||||
|> deliverOnMainQueue).start()
|
|> deliverOnMainQueue).start()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|
||||||
|
|> deliverOnMainQueue).start()
|
||||||
|
}
|
||||||
|
|
||||||
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||||
return
|
return
|
||||||
@ -558,11 +570,36 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
navigationController.setViewControllers(controllers, animated: true)
|
navigationController.setViewControllers(controllers, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let completion = component.completion {
|
if let completion = component.completion {
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let self, let transferStars = gift.transferStars, transferStars > 0, let starsContext = context.starsContext, let starsState = self.starsState {
|
||||||
|
if starsState.balance < StarsAmount(value: transferStars, nanos: 0) {
|
||||||
|
let _ = (self.optionsPromise.get()
|
||||||
|
|> filter { $0 != nil }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller] options in
|
||||||
|
let purchaseController = context.sharedContext.makeStarsPurchaseScreen(
|
||||||
|
context: context,
|
||||||
|
starsContext: starsContext,
|
||||||
|
options: options ?? [],
|
||||||
|
purpose: .transferStarGift(requiredStars: transferStars),
|
||||||
|
completion: { stars in
|
||||||
|
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||||
|
proceed(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
controller?.push(purchaseController)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
proceed(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proceed(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
controller.present(alertController, in: .window(.root))
|
controller.present(alertController, in: .window(.root))
|
||||||
}
|
}
|
||||||
@ -590,6 +627,11 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if let state = component.starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
|
||||||
|
self.optionsPromise.set(component.context.engine.payments.starsTopUpOptions()
|
||||||
|
|> map(Optional.init))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
@ -1237,6 +1279,11 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
if availableProducts.isEmpty {
|
if availableProducts.isEmpty {
|
||||||
var premiumProducts: [PremiumGiftProduct] = []
|
var premiumProducts: [PremiumGiftProduct] = []
|
||||||
for option in premiumOptions {
|
for option in premiumOptions {
|
||||||
|
if option.currency == "XTR" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let starsGiftOption = premiumOptions.first(where: { $0.currency == "XTR" && $0.months == option.months })
|
||||||
|
|
||||||
premiumProducts.append(
|
premiumProducts.append(
|
||||||
PremiumGiftProduct(
|
PremiumGiftProduct(
|
||||||
giftOption: CachedPremiumGiftOption(
|
giftOption: CachedPremiumGiftOption(
|
||||||
@ -1246,7 +1293,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
botUrl: "",
|
botUrl: "",
|
||||||
storeProductId: option.storeProductId
|
storeProductId: option.storeProductId
|
||||||
),
|
),
|
||||||
starsGiftOption: nil,
|
starsGiftOption: starsGiftOption,
|
||||||
storeProduct: nil,
|
storeProduct: nil,
|
||||||
discount: nil
|
discount: nil
|
||||||
)
|
)
|
||||||
|
@ -498,16 +498,8 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
|
|
||||||
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||||
|
|
||||||
let _ = (starsContext.state
|
let _ = (starsContext.onUpdate
|
||||||
|> take(until: { value in
|
|> deliverOnMainQueue).start(next: {
|
||||||
if let value {
|
|
||||||
if !value.flags.contains(.isPendingBalance) {
|
|
||||||
return SignalTakeAction(passthrough: true, complete: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SignalTakeAction(passthrough: false, complete: false)
|
|
||||||
})
|
|
||||||
|> deliverOnMainQueue).start(next: { _ in
|
|
||||||
proceed()
|
proceed()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
|
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
|
||||||
"//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent",
|
"//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent",
|
||||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -12,6 +12,7 @@ import AppBundle
|
|||||||
import AvatarNode
|
import AvatarNode
|
||||||
import Markdown
|
import Markdown
|
||||||
import GiftItemComponent
|
import GiftItemComponent
|
||||||
|
import ChatMessagePaymentAlertController
|
||||||
|
|
||||||
private final class GiftTransferAlertContentNode: AlertContentNode {
|
private final class GiftTransferAlertContentNode: AlertContentNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -251,7 +252,14 @@ private final class GiftTransferAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func giftTransferAlertController(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, transferStars: Int64, commit: @escaping () -> Void) -> AlertController {
|
public func giftTransferAlertController(
|
||||||
|
context: AccountContext,
|
||||||
|
gift: StarGift.UniqueGift,
|
||||||
|
peer: EnginePeer,
|
||||||
|
transferStars: Int64,
|
||||||
|
navigationController: NavigationController?,
|
||||||
|
commit: @escaping () -> Void
|
||||||
|
) -> AlertController {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let strings = presentationData.strings
|
let strings = presentationData.strings
|
||||||
|
|
||||||
@ -267,7 +275,6 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift.
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dismissImpl: ((Bool) -> Void)?
|
var dismissImpl: ((Bool) -> Void)?
|
||||||
var contentNode: GiftTransferAlertContentNode?
|
|
||||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: {
|
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: {
|
||||||
dismissImpl?(true)
|
dismissImpl?(true)
|
||||||
commit()
|
commit()
|
||||||
@ -275,9 +282,9 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift.
|
|||||||
dismissImpl?(true)
|
dismissImpl?(true)
|
||||||
})]
|
})]
|
||||||
|
|
||||||
contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
|
let contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
|
||||||
|
|
||||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
|
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode, navigationController: navigationController, showBalance: transferStars > 0)
|
||||||
dismissImpl = { [weak controller] animated in
|
dismissImpl = { [weak controller] animated in
|
||||||
if animated {
|
if animated {
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
|
@ -147,13 +147,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
|
|
||||||
var keepOriginalInfo = false
|
var keepOriginalInfo = false
|
||||||
|
|
||||||
private var optionsDisposable: Disposable?
|
private let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||||
private(set) var options: [StarsTopUpOption] = [] {
|
|
||||||
didSet {
|
|
||||||
self.optionsPromise.set(self.options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil)
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -269,13 +263,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
|
if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
|
||||||
self.optionsDisposable = (context.engine.payments.starsTopUpOptions()
|
self.optionsPromise.set(context.engine.payments.starsTopUpOptions()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] options in
|
|> map(Optional.init))
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.options = options
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,8 +340,12 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
self.inProgress = true
|
self.inProgress = true
|
||||||
self.updated()
|
self.updated()
|
||||||
|
|
||||||
|
if let controller = self.getController() as? GiftViewScreen {
|
||||||
|
controller.showBalance = false
|
||||||
|
}
|
||||||
|
|
||||||
self.upgradeDisposable = (self.upgradeGift(formId, self.keepOriginalInfo)
|
self.upgradeDisposable = (self.upgradeGift(formId, self.keepOriginalInfo)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self, weak starsContext] result in
|
||||||
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -363,6 +356,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
controller.subject = self.subject
|
controller.subject = self.subject
|
||||||
controller.animateSuccess()
|
controller.animateSuccess()
|
||||||
self.updated(transition: .spring(duration: 0.4))
|
self.updated(transition: .spring(duration: 0.4))
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.5) {
|
||||||
|
starsContext?.load(force: true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,11 +379,18 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
starsContext: starsContext,
|
starsContext: starsContext,
|
||||||
options: options ?? [],
|
options: options ?? [],
|
||||||
purpose: .upgradeStarGift(requiredStars: price),
|
purpose: .upgradeStarGift(requiredStars: price),
|
||||||
completion: { [weak starsContext] stars in
|
completion: { [weak self, weak starsContext] stars in
|
||||||
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
|
guard let self, let starsContext else {
|
||||||
Queue.mainQueue().after(2.0) {
|
return
|
||||||
proceed(upgradeForm.id)
|
|
||||||
}
|
}
|
||||||
|
self.inProgress = true
|
||||||
|
self.updated()
|
||||||
|
|
||||||
|
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||||
|
let _ = (starsContext.onUpdate
|
||||||
|
|> deliverOnMainQueue).start(next: {
|
||||||
|
proceed(upgradeForm.id)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
controller.push(purchaseController)
|
controller.push(purchaseController)
|
||||||
@ -2226,9 +2230,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0))
|
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0))
|
||||||
)
|
)
|
||||||
|
|
||||||
let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom)
|
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
|
||||||
|
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
|
||||||
return contentSize
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ public final class PeerInfoCoverComponent: Component {
|
|||||||
public let files: [Int64: TelegramMediaFile]
|
public let files: [Int64: TelegramMediaFile]
|
||||||
public let isDark: Bool
|
public let isDark: Bool
|
||||||
public let avatarCenter: CGPoint
|
public let avatarCenter: CGPoint
|
||||||
|
public let avatarSize: CGSize
|
||||||
public let avatarScale: CGFloat
|
public let avatarScale: CGFloat
|
||||||
public let defaultHeight: CGFloat
|
public let defaultHeight: CGFloat
|
||||||
public let gradientOnTop: Bool
|
public let gradientOnTop: Bool
|
||||||
@ -124,6 +125,7 @@ public final class PeerInfoCoverComponent: Component {
|
|||||||
files: [Int64: TelegramMediaFile],
|
files: [Int64: TelegramMediaFile],
|
||||||
isDark: Bool,
|
isDark: Bool,
|
||||||
avatarCenter: CGPoint,
|
avatarCenter: CGPoint,
|
||||||
|
avatarSize: CGSize = CGSize(width: 100.0, height: 100.0),
|
||||||
avatarScale: CGFloat,
|
avatarScale: CGFloat,
|
||||||
defaultHeight: CGFloat,
|
defaultHeight: CGFloat,
|
||||||
gradientOnTop: Bool = false,
|
gradientOnTop: Bool = false,
|
||||||
@ -136,6 +138,7 @@ public final class PeerInfoCoverComponent: Component {
|
|||||||
self.files = files
|
self.files = files
|
||||||
self.isDark = isDark
|
self.isDark = isDark
|
||||||
self.avatarCenter = avatarCenter
|
self.avatarCenter = avatarCenter
|
||||||
|
self.avatarSize = avatarSize
|
||||||
self.avatarScale = avatarScale
|
self.avatarScale = avatarScale
|
||||||
self.defaultHeight = defaultHeight
|
self.defaultHeight = defaultHeight
|
||||||
self.gradientOnTop = gradientOnTop
|
self.gradientOnTop = gradientOnTop
|
||||||
@ -160,6 +163,9 @@ public final class PeerInfoCoverComponent: Component {
|
|||||||
if lhs.avatarCenter != rhs.avatarCenter {
|
if lhs.avatarCenter != rhs.avatarCenter {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.avatarSize != rhs.avatarSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.avatarScale != rhs.avatarScale {
|
if lhs.avatarScale != rhs.avatarScale {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -492,7 +498,7 @@ public final class PeerInfoCoverComponent: Component {
|
|||||||
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame)
|
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame)
|
||||||
transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction)
|
transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction)
|
||||||
|
|
||||||
var baseDistance: CGFloat = 72.0
|
var baseDistance: CGFloat = component.avatarSize.width / 2.0 + 22.0
|
||||||
var baseRowDistance: CGFloat = 28.0
|
var baseRowDistance: CGFloat = 28.0
|
||||||
var baseItemSize: CGFloat = 26.0
|
var baseItemSize: CGFloat = 26.0
|
||||||
if availableSize.width <= 60.0 {
|
if availableSize.width <= 60.0 {
|
||||||
@ -516,7 +522,7 @@ public final class PeerInfoCoverComponent: Component {
|
|||||||
let baseItemDistance: CGFloat = baseDistance + CGFloat(row) * baseRowDistance
|
let baseItemDistance: CGFloat = baseDistance + CGFloat(row) * baseRowDistance
|
||||||
|
|
||||||
let itemDistanceFraction = max(0.0, min(1.0, baseItemDistance / (baseDistance * 2.0)))
|
let itemDistanceFraction = max(0.0, min(1.0, baseItemDistance / (baseDistance * 2.0)))
|
||||||
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: itemDistanceFraction, reverse: false)
|
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction * 1.6, t: itemDistanceFraction, reverse: false)
|
||||||
let itemDistance = baseItemDistance * (1.0 - itemScaleFraction) + 20.0 * itemScaleFraction
|
let itemDistance = baseItemDistance * (1.0 - itemScaleFraction) + 20.0 * itemScaleFraction
|
||||||
|
|
||||||
var itemAngle: CGFloat
|
var itemAngle: CGFloat
|
||||||
|
@ -19,6 +19,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
public let giftsContext: ProfileGiftsContext
|
public let giftsContext: ProfileGiftsContext
|
||||||
public let hasBackground: Bool
|
public let hasBackground: Bool
|
||||||
public let avatarCenter: CGPoint
|
public let avatarCenter: CGPoint
|
||||||
|
public let avatarSize: CGSize
|
||||||
public let defaultHeight: CGFloat
|
public let defaultHeight: CGFloat
|
||||||
public let avatarTransitionFraction: CGFloat
|
public let avatarTransitionFraction: CGFloat
|
||||||
public let statusBarHeight: CGFloat
|
public let statusBarHeight: CGFloat
|
||||||
@ -34,6 +35,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
giftsContext: ProfileGiftsContext,
|
giftsContext: ProfileGiftsContext,
|
||||||
hasBackground: Bool,
|
hasBackground: Bool,
|
||||||
avatarCenter: CGPoint,
|
avatarCenter: CGPoint,
|
||||||
|
avatarSize: CGSize,
|
||||||
defaultHeight: CGFloat,
|
defaultHeight: CGFloat,
|
||||||
avatarTransitionFraction: CGFloat,
|
avatarTransitionFraction: CGFloat,
|
||||||
statusBarHeight: CGFloat,
|
statusBarHeight: CGFloat,
|
||||||
@ -48,6 +50,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
self.giftsContext = giftsContext
|
self.giftsContext = giftsContext
|
||||||
self.hasBackground = hasBackground
|
self.hasBackground = hasBackground
|
||||||
self.avatarCenter = avatarCenter
|
self.avatarCenter = avatarCenter
|
||||||
|
self.avatarSize = avatarSize
|
||||||
self.defaultHeight = defaultHeight
|
self.defaultHeight = defaultHeight
|
||||||
self.avatarTransitionFraction = avatarTransitionFraction
|
self.avatarTransitionFraction = avatarTransitionFraction
|
||||||
self.statusBarHeight = statusBarHeight
|
self.statusBarHeight = statusBarHeight
|
||||||
@ -71,6 +74,9 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
if lhs.avatarCenter != rhs.avatarCenter {
|
if lhs.avatarCenter != rhs.avatarCenter {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.avatarSize != rhs.avatarSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.defaultHeight != rhs.defaultHeight {
|
if lhs.defaultHeight != rhs.defaultHeight {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -213,14 +219,14 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
}
|
}
|
||||||
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.statusBarHeight), size: component.topLeftButtonsSize))
|
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.statusBarHeight), size: component.topLeftButtonsSize))
|
||||||
excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - component.topRightButtonsSize.width, y: component.statusBarHeight), size: component.topRightButtonsSize))
|
excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - component.topRightButtonsSize.width, y: component.statusBarHeight), size: component.topRightButtonsSize))
|
||||||
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + 56.0), size: CGSize(width: component.titleWidth, height: 72.0)))
|
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + component.avatarSize.height / 2.0 + 6.0), size: CGSize(width: component.titleWidth, height: 100.0)))
|
||||||
if component.bottomHeight > 0.0 {
|
if component.bottomHeight > 0.0 {
|
||||||
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.defaultHeight - component.bottomHeight), size: CGSize(width: availableSize.width, height: component.bottomHeight)))
|
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.defaultHeight - component.bottomHeight), size: CGSize(width: availableSize.width, height: component.bottomHeight)))
|
||||||
}
|
}
|
||||||
|
|
||||||
let positionGenerator = PositionGenerator(
|
let positionGenerator = PositionGenerator(
|
||||||
containerSize: CGSize(width: availableSize.width, height: component.defaultHeight),
|
containerSize: CGSize(width: availableSize.width, height: component.defaultHeight),
|
||||||
centerFrame: CGSize(width: 100, height: 100).centered(around: avatarCenter),
|
centerFrame: component.avatarSize.centered(around: avatarCenter),
|
||||||
exclusionZones: excludeRects,
|
exclusionZones: excludeRects,
|
||||||
minimumDistance: 42.0,
|
minimumDistance: 42.0,
|
||||||
edgePadding: 5.0,
|
edgePadding: 5.0,
|
||||||
@ -298,26 +304,28 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
|||||||
}
|
}
|
||||||
iconLayer.glowing = component.hasBackground
|
iconLayer.glowing = component.hasBackground
|
||||||
|
|
||||||
let centerPosition = component.avatarCenter
|
let itemDistanceFraction = max(0.0, min(0.5, (iconPosition.distance - component.avatarSize.width / 2.0) / 144.0))
|
||||||
let finalPosition = iconPosition.center.offsetBy(dx: component.avatarCenter.x, dy: component.avatarCenter.y)
|
let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.33), t: itemDistanceFraction, reverse: false)
|
||||||
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: 0.0, reverse: false)
|
|
||||||
|
|
||||||
func interpolateRect(from: CGPoint, to: CGPoint, t: CGFloat) -> CGPoint {
|
func interpolatePosition(from: PositionGenerator.Position, to: PositionGenerator.Position, t: CGFloat) -> PositionGenerator.Position {
|
||||||
let clampedT = max(0, min(1, t))
|
let clampedT = max(0, min(1, t))
|
||||||
|
|
||||||
let interpolatedX = from.x + (to.x - from.x) * clampedT
|
let interpolatedDistance = from.distance + (to.distance - from.distance) * clampedT
|
||||||
let interpolatedY = from.y + (to.y - from.y) * clampedT
|
let interpolatedAngle = from.angle + (to.angle - from.angle) * clampedT
|
||||||
|
|
||||||
return CGPoint(
|
return PositionGenerator.Position(distance: interpolatedDistance, angle: interpolatedAngle, scale: from.scale)
|
||||||
x: interpolatedX,
|
|
||||||
y: interpolatedY
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let effectivePosition = interpolateRect(from: finalPosition, to: centerPosition, t: itemScaleFraction)
|
let toAngle: CGFloat = .pi * 0.18
|
||||||
|
let centerPosition = PositionGenerator.Position(distance: 0.0, angle: iconPosition.angle + toAngle, scale: iconPosition.scale)
|
||||||
|
let effectivePosition = interpolatePosition(from: iconPosition, to: centerPosition, t: itemScaleFraction)
|
||||||
|
let effectiveAngle = toAngle * itemScaleFraction
|
||||||
|
|
||||||
|
let absolutePosition = getAbsolutePosition(position: effectivePosition, centerPoint: component.avatarCenter)
|
||||||
|
|
||||||
iconTransition.setBounds(layer: iconLayer, bounds: CGRect(origin: .zero, size: iconSize))
|
iconTransition.setBounds(layer: iconLayer, bounds: CGRect(origin: .zero, size: iconSize))
|
||||||
iconTransition.setPosition(layer: iconLayer, position: effectivePosition)
|
iconTransition.setPosition(layer: iconLayer, position: absolutePosition)
|
||||||
|
iconLayer.updateRotation(effectiveAngle, transition: iconTransition)
|
||||||
iconTransition.setScale(layer: iconLayer, scale: iconPosition.scale * (1.0 - itemScaleFraction))
|
iconTransition.setScale(layer: iconLayer, scale: iconPosition.scale * (1.0 - itemScaleFraction))
|
||||||
iconTransition.setAlpha(layer: iconLayer, alpha: 1.0 - itemScaleFraction)
|
iconTransition.setAlpha(layer: iconLayer, alpha: 1.0 - itemScaleFraction)
|
||||||
|
|
||||||
@ -580,7 +588,12 @@ private class GiftIconLayer: SimpleLayer {
|
|||||||
|
|
||||||
override func layoutSublayers() {
|
override func layoutSublayers() {
|
||||||
self.shadowLayer.frame = CGRect(origin: .zero, size: self.bounds.size).insetBy(dx: -8.0, dy: -8.0)
|
self.shadowLayer.frame = CGRect(origin: .zero, size: self.bounds.size).insetBy(dx: -8.0, dy: -8.0)
|
||||||
self.animationLayer.frame = CGRect(origin: .zero, size: self.bounds.size)
|
self.animationLayer.bounds = CGRect(origin: .zero, size: self.bounds.size)
|
||||||
|
self.animationLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateRotation(_ angle: CGFloat, transition: ComponentTransition) {
|
||||||
|
self.animationLayer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startAnimations(index: Int) {
|
func startAnimations(index: Int) {
|
||||||
@ -638,8 +651,16 @@ private class GiftIconLayer: SimpleLayer {
|
|||||||
|
|
||||||
private struct PositionGenerator {
|
private struct PositionGenerator {
|
||||||
struct Position {
|
struct Position {
|
||||||
let center: CGPoint
|
let distance: CGFloat
|
||||||
|
let angle: CGFloat
|
||||||
let scale: CGFloat
|
let scale: CGFloat
|
||||||
|
|
||||||
|
var relativeCartesian: CGPoint {
|
||||||
|
return CGPoint(
|
||||||
|
x: self.distance * cos(self.angle),
|
||||||
|
y: self.distance * sin(self.angle)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let containerSize: CGSize
|
let containerSize: CGSize
|
||||||
@ -700,15 +721,13 @@ private struct PositionGenerator {
|
|||||||
|
|
||||||
let orbitRangeSize = self.innerOrbitRange.max - self.innerOrbitRange.min
|
let orbitRangeSize = self.innerOrbitRange.max - self.innerOrbitRange.min
|
||||||
let orbitDistanceFactor = self.innerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
|
let orbitDistanceFactor = self.innerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
|
||||||
let orbitDistance = orbitDistanceFactor * centerRadius
|
let distance = orbitDistanceFactor * centerRadius
|
||||||
|
|
||||||
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
|
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
|
||||||
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
|
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
|
||||||
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
|
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
|
||||||
|
|
||||||
let absoluteX = centerPoint.x + orbitDistance * cos(angle)
|
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
|
||||||
let absoluteY = centerPoint.y + orbitDistance * sin(angle)
|
|
||||||
let absolutePosition = CGPoint(x: absoluteX, y: absoluteY)
|
|
||||||
|
|
||||||
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
|
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
|
||||||
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
|
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
|
||||||
@ -717,11 +736,6 @@ private struct PositionGenerator {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let relativePosition = CGPoint(
|
|
||||||
x: absolutePosition.x - centerPoint.x,
|
|
||||||
y: absolutePosition.y - centerPoint.y
|
|
||||||
)
|
|
||||||
|
|
||||||
let itemRect = CGRect(
|
let itemRect = CGRect(
|
||||||
x: absolutePosition.x - itemSize.width/2,
|
x: absolutePosition.x - itemSize.width/2,
|
||||||
y: absolutePosition.y - itemSize.height/2,
|
y: absolutePosition.y - itemSize.height/2,
|
||||||
@ -729,10 +743,12 @@ private struct PositionGenerator {
|
|||||||
height: itemSize.height
|
height: itemSize.height
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) {
|
if self.isValidPosition(itemRect, existingPositions: positions.map {
|
||||||
|
getAbsolutePosition(distance: $0.distance, angle: $0.angle, centerPoint: centerPoint)
|
||||||
|
}, itemSize: itemSize) {
|
||||||
let scaleRangeSize = max(self.scaleRange.min + 0.1, 0.75) - self.scaleRange.max
|
let scaleRangeSize = max(self.scaleRange.min + 0.1, 0.75) - self.scaleRange.max
|
||||||
let scale = self.scaleRange.max + scaleRangeSize * CGFloat(self.lokiRng.next())
|
let scale = self.scaleRange.max + scaleRangeSize * CGFloat(self.lokiRng.next())
|
||||||
positions.append(Position(center: relativePosition, scale: scale))
|
positions.append(Position(distance: distance, angle: angle, scale: scale))
|
||||||
|
|
||||||
if absolutePosition.x < centerPoint.x {
|
if absolutePosition.x < centerPoint.x {
|
||||||
leftPositions += 1
|
leftPositions += 1
|
||||||
@ -751,16 +767,13 @@ private struct PositionGenerator {
|
|||||||
|
|
||||||
let orbitRangeSize = self.outerOrbitRange.max - self.outerOrbitRange.min
|
let orbitRangeSize = self.outerOrbitRange.max - self.outerOrbitRange.min
|
||||||
let orbitDistanceFactor = self.outerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
|
let orbitDistanceFactor = self.outerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
|
||||||
let orbitDistance = orbitDistanceFactor * centerRadius
|
let distance = orbitDistanceFactor * centerRadius
|
||||||
|
|
||||||
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
|
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
|
||||||
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
|
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
|
||||||
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
|
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
|
||||||
|
|
||||||
let absoluteX = centerPoint.x + orbitDistance * cos(angle)
|
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
|
||||||
let absoluteY = centerPoint.y + orbitDistance * sin(angle)
|
|
||||||
let absolutePosition = CGPoint(x: absoluteX, y: absoluteY)
|
|
||||||
|
|
||||||
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
|
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
|
||||||
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
|
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
|
||||||
absolutePosition.y - itemSize.height/2 < self.edgePadding ||
|
absolutePosition.y - itemSize.height/2 < self.edgePadding ||
|
||||||
@ -768,11 +781,6 @@ private struct PositionGenerator {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let relativePosition = CGPoint(
|
|
||||||
x: absolutePosition.x - centerPoint.x,
|
|
||||||
y: absolutePosition.y - centerPoint.y
|
|
||||||
)
|
|
||||||
|
|
||||||
let itemRect = CGRect(
|
let itemRect = CGRect(
|
||||||
x: absolutePosition.x - itemSize.width/2,
|
x: absolutePosition.x - itemSize.width/2,
|
||||||
y: absolutePosition.y - itemSize.height/2,
|
y: absolutePosition.y - itemSize.height/2,
|
||||||
@ -780,12 +788,12 @@ private struct PositionGenerator {
|
|||||||
height: itemSize.height
|
height: itemSize.height
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) {
|
if self.isValidPosition(itemRect, existingPositions: positions.map {
|
||||||
let distance = hypot(absolutePosition.x - centerPoint.x, absolutePosition.y - centerPoint.y)
|
getAbsolutePosition(distance: $0.distance, angle: $0.angle, centerPoint: centerPoint)
|
||||||
|
}, itemSize: itemSize) {
|
||||||
let normalizedDistance = min(distance / maxPossibleDistance, 1.0)
|
let normalizedDistance = min(distance / maxPossibleDistance, 1.0)
|
||||||
let scale = self.scaleRange.max - normalizedDistance * (self.scaleRange.max - self.scaleRange.min)
|
let scale = self.scaleRange.max - normalizedDistance * (self.scaleRange.max - self.scaleRange.min)
|
||||||
positions.append(Position(center: relativePosition, scale: scale))
|
positions.append(Position(distance: distance, angle: angle, scale: scale))
|
||||||
|
|
||||||
if absolutePosition.x < centerPoint.x {
|
if absolutePosition.x < centerPoint.x {
|
||||||
leftPositions += 1
|
leftPositions += 1
|
||||||
@ -798,8 +806,11 @@ private struct PositionGenerator {
|
|||||||
return positions
|
return positions
|
||||||
}
|
}
|
||||||
|
|
||||||
private func posToAbsolute(_ relativePos: CGPoint, centerPoint: CGPoint) -> CGPoint {
|
func getAbsolutePosition(distance: CGFloat, angle: CGFloat, centerPoint: CGPoint) -> CGPoint {
|
||||||
return CGPoint(x: relativePos.x + centerPoint.x, y: relativePos.y + centerPoint.y)
|
return CGPoint(
|
||||||
|
x: centerPoint.x + distance * cos(angle),
|
||||||
|
y: centerPoint.y + distance * sin(angle)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func isValidPosition(_ rect: CGRect, existingPositions: [CGPoint], itemSize: CGSize) -> Bool {
|
private func isValidPosition(_ rect: CGRect, existingPositions: [CGPoint], itemSize: CGSize) -> Bool {
|
||||||
@ -827,6 +838,20 @@ private struct PositionGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func getAbsolutePosition(position: PositionGenerator.Position, centerPoint: CGPoint) -> CGPoint {
|
||||||
|
return CGPoint(
|
||||||
|
x: centerPoint.x + position.distance * cos(position.angle),
|
||||||
|
y: centerPoint.y + position.distance * sin(position.angle)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getAbsolutePosition(distance: CGFloat, angle: CGFloat, centerPoint: CGPoint) -> CGPoint {
|
||||||
|
return CGPoint(
|
||||||
|
x: centerPoint.x + distance * cos(angle),
|
||||||
|
y: centerPoint.y + distance * sin(angle)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private func windowFunction(t: CGFloat) -> CGFloat {
|
private func windowFunction(t: CGFloat) -> CGFloat {
|
||||||
return bezierPoint(0.6, 0.0, 0.4, 1.0, t)
|
return bezierPoint(0.6, 0.0, 0.4, 1.0, t)
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
|||||||
|
|
||||||
func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
let sideInset: CGFloat = 24.0
|
let sideInset: CGFloat = 24.0
|
||||||
|
let expandedSideInset: CGFloat = 16.0
|
||||||
|
|
||||||
let maximumExpandOffset: CGFloat = 14.0
|
let maximumExpandOffset: CGFloat = 14.0
|
||||||
let expandOffset: CGFloat = -expandFraction * maximumExpandOffset
|
let expandOffset: CGFloat = -expandFraction * maximumExpandOffset
|
||||||
@ -188,7 +189,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
|||||||
self.currentRightButtons = rightButtons
|
self.currentRightButtons = rightButtons
|
||||||
|
|
||||||
var nextRegularButtonOrigin = size.width - sideInset - 8.0
|
var nextRegularButtonOrigin = size.width - sideInset - 8.0
|
||||||
var nextExpandedButtonOrigin = size.width - sideInset - 8.0
|
var nextExpandedButtonOrigin = size.width - expandedSideInset
|
||||||
for spec in rightButtons.reversed() {
|
for spec in rightButtons.reversed() {
|
||||||
let buttonNode: PeerInfoHeaderNavigationButton
|
let buttonNode: PeerInfoHeaderNavigationButton
|
||||||
var wasAdded = false
|
var wasAdded = false
|
||||||
@ -257,7 +258,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
|||||||
for key in removeKeys {
|
for key in removeKeys {
|
||||||
if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) {
|
if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) {
|
||||||
if key == .moreSearchSort || key == .searchWithTags || key == .standaloneSearch {
|
if key == .moreSearchSort || key == .searchWithTags || key == .standaloneSearch {
|
||||||
buttonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in
|
buttonNode.layer.animateAlpha(from: buttonNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in
|
||||||
buttonNode?.removeFromSupernode()
|
buttonNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
buttonNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
buttonNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||||
@ -268,7 +269,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var nextRegularButtonOrigin = size.width - sideInset - 8.0
|
var nextRegularButtonOrigin = size.width - sideInset - 8.0
|
||||||
var nextExpandedButtonOrigin = size.width - sideInset - 8.0
|
var nextExpandedButtonOrigin = size.width - expandedSideInset
|
||||||
|
|
||||||
for spec in rightButtons.reversed() {
|
for spec in rightButtons.reversed() {
|
||||||
var key = spec.key
|
var key = spec.key
|
||||||
|
@ -2289,6 +2289,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
if !buttonKeys.isEmpty {
|
if !buttonKeys.isEmpty {
|
||||||
backgroundDefaultHeight = 327.0
|
backgroundDefaultHeight = 327.0
|
||||||
|
if metrics.isTablet {
|
||||||
|
backgroundDefaultHeight += 60.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hasBackground = true
|
hasBackground = true
|
||||||
} else if let peer {
|
} else if let peer {
|
||||||
@ -2308,6 +2311,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
files: [:],
|
files: [:],
|
||||||
isDark: presentationData.theme.overallDarkAppearance,
|
isDark: presentationData.theme.overallDarkAppearance,
|
||||||
avatarCenter: apparentAvatarFrame.center.offsetBy(dx: bannerInset, dy: 0.0),
|
avatarCenter: apparentAvatarFrame.center.offsetBy(dx: bannerInset, dy: 0.0),
|
||||||
|
avatarSize: apparentAvatarFrame.size,
|
||||||
avatarScale: avatarScale,
|
avatarScale: avatarScale,
|
||||||
defaultHeight: backgroundDefaultHeight,
|
defaultHeight: backgroundDefaultHeight,
|
||||||
gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45),
|
gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45),
|
||||||
@ -2352,6 +2356,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
giftsContext: profileGiftsContext,
|
giftsContext: profileGiftsContext,
|
||||||
hasBackground: hasBackground,
|
hasBackground: hasBackground,
|
||||||
avatarCenter: apparentAvatarFrame.center,
|
avatarCenter: apparentAvatarFrame.center,
|
||||||
|
avatarSize: apparentAvatarFrame.size,
|
||||||
defaultHeight: backgroundDefaultHeight,
|
defaultHeight: backgroundDefaultHeight,
|
||||||
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
|
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
|
||||||
statusBarHeight: statusBarHeight,
|
statusBarHeight: statusBarHeight,
|
||||||
|
@ -379,7 +379,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
let optionSpacing: CGFloat = 10.0
|
let optionSpacing: CGFloat = 10.0
|
||||||
let itemsSideInset = params.sideInset + 16.0
|
let itemsSideInset = params.sideInset + 16.0
|
||||||
|
|
||||||
let defaultItemsInRow = params.size.width > params.size.height ? 5 : 3
|
let defaultItemsInRow = params.size.width > params.size.height || params.size.width > 414.0 ? 5 : 3
|
||||||
let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow))
|
let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow))
|
||||||
let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow)
|
let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow)
|
||||||
let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
|
let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
|
||||||
@ -613,7 +613,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
var bottomScrollInset: CGFloat = 0.0
|
var bottomScrollInset: CGFloat = 0.0
|
||||||
var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0
|
var contentHeight = ceil(CGFloat(starsProducts.count) / CGFloat(defaultItemsInRow)) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0
|
||||||
|
|
||||||
let size = params.size
|
let size = params.size
|
||||||
let sideInset = params.sideInset
|
let sideInset = params.sideInset
|
||||||
@ -677,13 +677,14 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
|
|
||||||
let buttonSideInset = sideInset + 16.0
|
let buttonSideInset = sideInset + 16.0
|
||||||
let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0)
|
let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0)
|
||||||
var bottomPanelHeight = max(8.0, bottomInset) + buttonSize.height + 8.0
|
let effectiveBottomInset = max(8.0, bottomInset)
|
||||||
|
var bottomPanelHeight = effectiveBottomInset + buttonSize.height + 8.0
|
||||||
if params.visibleHeight < 110.0 {
|
if params.visibleHeight < 110.0 {
|
||||||
scrollOffset -= bottomPanelHeight
|
scrollOffset -= bottomPanelHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
let panelTransition = ComponentTransition.immediate
|
let panelTransition = ComponentTransition.immediate
|
||||||
panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize))
|
panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - effectiveBottomInset - buttonSize.height - scrollOffset), size: buttonSize))
|
||||||
panelTransition.setAlpha(view: panelButton.view, alpha: panelAlpha)
|
panelTransition.setAlpha(view: panelButton.view, alpha: panelAlpha)
|
||||||
let _ = panelButton.updateLayout(width: buttonSize.width, transition: .immediate)
|
let _ = panelButton.updateLayout(width: buttonSize.width, transition: .immediate)
|
||||||
|
|
||||||
@ -754,7 +755,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
if panelCheckView.superview == nil {
|
if panelCheckView.superview == nil {
|
||||||
self.view.addSubview(panelCheckView)
|
self.view.addSubview(panelCheckView)
|
||||||
}
|
}
|
||||||
panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - bottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize)
|
panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - effectiveBottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize)
|
||||||
panelTransition.setAlpha(view: panelCheckView, alpha: panelAlpha)
|
panelTransition.setAlpha(view: panelCheckView, alpha: panelAlpha)
|
||||||
}
|
}
|
||||||
panelButton.isHidden = true
|
panelButton.isHidden = true
|
||||||
@ -998,7 +999,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
if canReorder {
|
if case .unique = gift.gift, canReorder {
|
||||||
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Context_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Context_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||||
c?.dismiss(completion: { [weak self] in
|
c?.dismiss(completion: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1015,7 +1016,26 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if self.context.isPremium {
|
||||||
let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone()
|
let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone()
|
||||||
|
} else {
|
||||||
|
let text = strings.Gift_View_TooltipPremiumWearing
|
||||||
|
let tooltipController = UndoOverlayController(
|
||||||
|
presentationData: presentationData,
|
||||||
|
content: .premiumPaywall(title: nil, text: text, customUndoText: nil, timeout: nil, linkAction: nil),
|
||||||
|
position: .bottom,
|
||||||
|
animateInAsReplacement: false,
|
||||||
|
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
|
||||||
|
action: { [weak self] action in
|
||||||
|
if let self, case .info = action {
|
||||||
|
let premiumController = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .messageEffects, forceDark: false, dismissed: nil)
|
||||||
|
self.parentController?.push(premiumController)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.parentController?.present(tooltipController, in: .current)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ swift_library(
|
|||||||
"//submodules/AsyncDisplayKit",
|
"//submodules/AsyncDisplayKit",
|
||||||
"//submodules/Display",
|
"//submodules/Display",
|
||||||
"//submodules/TelegramCore",
|
"//submodules/TelegramCore",
|
||||||
|
"//submodules/SSignalKit/SwiftSignalKit",
|
||||||
"//submodules/AccountContext",
|
"//submodules/AccountContext",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import SwiftSignalKit
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
@ -38,6 +39,10 @@ public final class StarsBalanceOverlayComponent: Component {
|
|||||||
private let action = ComponentView<Empty>()
|
private let action = ComponentView<Empty>()
|
||||||
|
|
||||||
private var component: StarsBalanceOverlayComponent?
|
private var component: StarsBalanceOverlayComponent?
|
||||||
|
private var state: EmptyComponentState?
|
||||||
|
|
||||||
|
private var balance: Int64 = 0
|
||||||
|
private var balanceDisposable: Disposable?
|
||||||
|
|
||||||
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||||
|
|
||||||
@ -53,17 +58,43 @@ public final class StarsBalanceOverlayComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.balanceDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func tapped() {
|
@objc private func tapped() {
|
||||||
if let component = self.component {
|
if let component = self.component {
|
||||||
component.action()
|
component.action()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isUpdating = false
|
||||||
func update(component: StarsBalanceOverlayComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: StarsBalanceOverlayComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.isUpdating = true
|
||||||
|
defer {
|
||||||
|
self.isUpdating = false
|
||||||
|
}
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
|
if self.balanceDisposable == nil, let starsContext = component.context.starsContext {
|
||||||
|
self.balanceDisposable = (starsContext.state
|
||||||
|
|> map { state -> Int64 in
|
||||||
|
return state?.balance.value ?? 0
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] balance in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.balance = balance
|
||||||
|
if !self.isUpdating {
|
||||||
|
self.state?.updated()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let balance = presentationStringsFormattedNumber(Int32(component.context.starsContext?.currentState?.balance.value ?? 0), presentationData.dateTimeFormat.groupingSeparator)
|
let balance = presentationStringsFormattedNumber(Int32(self.balance), presentationData.dateTimeFormat.groupingSeparator)
|
||||||
|
|
||||||
let attributedText = parseMarkdownIntoAttributedString(
|
let attributedText = parseMarkdownIntoAttributedString(
|
||||||
presentationData.strings.StarsBalance_YourBalance("**⭐️\(balance)**").string,
|
presentationData.strings.StarsBalance_YourBalance("**⭐️\(balance)**").string,
|
||||||
@ -121,23 +152,27 @@ public final class StarsBalanceOverlayComponent: Component {
|
|||||||
|
|
||||||
if let textView = self.text.view {
|
if let textView = self.text.view {
|
||||||
if textView.superview == nil {
|
if textView.superview == nil {
|
||||||
self.addSubview(textView)
|
self.backgroundView.addSubview(textView)
|
||||||
}
|
}
|
||||||
textView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: 10.0), size: textSize)
|
textView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: 10.0), size: textSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let actionView = self.action.view {
|
if let actionView = self.action.view {
|
||||||
if actionView.superview == nil {
|
if actionView.superview == nil {
|
||||||
self.addSubview(actionView)
|
self.backgroundView.addSubview(actionView)
|
||||||
}
|
}
|
||||||
actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 29.0), size: actionSize)
|
actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 29.0), size: actionSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate)
|
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate)
|
||||||
self.backgroundView.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate)
|
self.backgroundView.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate)
|
||||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
|
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - size.width) / 2.0), y: 0.0), size: size))
|
||||||
|
|
||||||
return size
|
return CGSize(width: availableSize.width, height: size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||||
|
return self.backgroundView.frame.contains(point)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +241,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
|
|||||||
textString = strings.Stars_Purchase_StarGiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
|
textString = strings.Stars_Purchase_StarGiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
|
||||||
case .upgradeStarGift:
|
case .upgradeStarGift:
|
||||||
textString = strings.Stars_Purchase_UpgradeStarGiftInfo
|
textString = strings.Stars_Purchase_UpgradeStarGiftInfo
|
||||||
|
case .transferStarGift:
|
||||||
|
textString = strings.Stars_Purchase_TransferStarGiftInfo
|
||||||
case let .sendMessage(peerId, _):
|
case let .sendMessage(peerId, _):
|
||||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||||
textString = strings.Stars_Purchase_SendMessageInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
|
textString = strings.Stars_Purchase_SendMessageInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
|
||||||
@ -828,7 +830,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent {
|
|||||||
titleText = strings.Stars_Purchase_GetStars
|
titleText = strings.Stars_Purchase_GetStars
|
||||||
case .gift:
|
case .gift:
|
||||||
titleText = strings.Stars_Purchase_GiftStars
|
titleText = strings.Stars_Purchase_GiftStars
|
||||||
case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .sendMessage(_, requiredStars):
|
case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars):
|
||||||
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
|
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1274,6 +1276,8 @@ private extension StarsPurchasePurpose {
|
|||||||
return requiredStars
|
return requiredStars
|
||||||
case let .upgradeStarGift(requiredStars):
|
case let .upgradeStarGift(requiredStars):
|
||||||
return requiredStars
|
return requiredStars
|
||||||
|
case let .transferStarGift(requiredStars):
|
||||||
|
return requiredStars
|
||||||
case let .sendMessage(_, requiredStars):
|
case let .sendMessage(_, requiredStars):
|
||||||
return requiredStars
|
return requiredStars
|
||||||
default:
|
default:
|
||||||
|
@ -1530,15 +1530,15 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
||||||
)
|
)
|
||||||
originY += button.size.height
|
originY += button.size.height
|
||||||
|
originY += 7.0
|
||||||
}
|
}
|
||||||
|
|
||||||
context.add(closeButton
|
context.add(closeButton
|
||||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0))
|
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0))
|
||||||
)
|
)
|
||||||
|
|
||||||
let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom)
|
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
|
||||||
|
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
|
||||||
return contentSize
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,10 +589,15 @@ private final class SheetContent: CombinedComponent {
|
|||||||
options: state?.options ?? [],
|
options: state?.options ?? [],
|
||||||
purpose: purpose,
|
purpose: purpose,
|
||||||
completion: { [weak starsContext] stars in
|
completion: { [weak starsContext] stars in
|
||||||
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
|
guard let starsContext else {
|
||||||
Queue.mainQueue().after(0.1) {
|
return
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
|
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||||
|
|
||||||
|
let _ = (starsContext.onUpdate
|
||||||
|
|> deliverOnMainQueue).start(next: {
|
||||||
|
completion()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
controller?.push(purchaseController)
|
controller?.push(purchaseController)
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Message/StarsCount.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/StarsCount.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "msgstar.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/StarsCount.imageset/msgstar.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Message/StarsCount.imageset/msgstar.pdf
vendored
Normal file
Binary file not shown.
@ -354,10 +354,11 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
let _ = currentAppConfiguration.swap(value)
|
let _ = currentAppConfiguration.swap(value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
|
||||||
self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes()))
|
self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes()))
|
||||||
if !temp {
|
if !temp {
|
||||||
let currentCountriesConfiguration = self.currentCountriesConfiguration
|
let currentCountriesConfiguration = self.currentCountriesConfiguration
|
||||||
self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: nil)
|
self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: langCode)
|
||||||
|> deliverOnMainQueue).start(next: { value in
|
|> deliverOnMainQueue).start(next: { value in
|
||||||
let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value))
|
let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value))
|
||||||
})
|
})
|
||||||
|
@ -13,6 +13,7 @@ import PresentationDataUtils
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import UrlHandling
|
import UrlHandling
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import ChatInterfaceState
|
||||||
|
|
||||||
func openWebAppImpl(
|
func openWebAppImpl(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -182,7 +183,7 @@ func openWebAppImpl(
|
|||||||
var isInline = false
|
var isInline = false
|
||||||
var botId = botPeer.id
|
var botId = botPeer.id
|
||||||
var botName = botName
|
var botName = botName
|
||||||
var botAddress = ""
|
var botAddress = botPeer.addressName ?? ""
|
||||||
var botVerified = botPeer.isVerified
|
var botVerified = botPeer.isVerified
|
||||||
if case let .inline(bot) = source {
|
if case let .inline(bot) = source {
|
||||||
isInline = true
|
isInline = true
|
||||||
@ -367,8 +368,20 @@ public extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let inputString = "@\(botAddress) \(query)"
|
||||||
if let chatController {
|
if let chatController {
|
||||||
chatController.controllerInteraction?.activateSwitchInline(selectedPeer?.id ?? peerId, "@\(botAddress) \(query)", nil)
|
chatController.controllerInteraction?.activateSwitchInline(selectedPeer?.id ?? peerId, inputString, nil)
|
||||||
|
} else if let selectedPeer, let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||||
|
let textInputState = ChatTextInputState(inputText: NSAttributedString(string: inputString))
|
||||||
|
let _ = (ChatInterfaceState.update(engine: context.engine, peerId: selectedPeer.id, threadId: nil, { currentState in
|
||||||
|
return currentState.withUpdatedComposeInputState(textInputState)
|
||||||
|
})
|
||||||
|
|> deliverOnMainQueue).startStandalone(completed: { [weak navigationController] in
|
||||||
|
guard let navigationController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(selectedPeer), subject: nil, updateTextInputState: textInputState, peekData: nil))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +72,11 @@ extension ChatControllerImpl {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), completion: { _ in
|
let controller = self.context.sharedContext.makeStarsPurchaseScreen(context: self.context, starsContext: starsContext, options: options, purpose: .sendMessage(peerId: peer.id, requiredStars: totalAmount), completion: { _ in
|
||||||
|
let _ = (starsContext.onUpdate
|
||||||
|
|> deliverOnMainQueue).start(next: {
|
||||||
completion(false)
|
completion(false)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
self.push(controller)
|
self.push(controller)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -5786,7 +5786,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if case let .known(value) = cachedData.businessIntro {
|
if case let .known(value) = cachedData.businessIntro {
|
||||||
businessIntro = value
|
businessIntro = value
|
||||||
}
|
}
|
||||||
|
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
|
} else {
|
||||||
sendPaidMessageStars = cachedData.sendPaidMessageStars
|
sendPaidMessageStars = cachedData.sendPaidMessageStars
|
||||||
|
}
|
||||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||||
var invitedBy: Peer?
|
var invitedBy: Peer?
|
||||||
if let invitedByPeerId = cachedData.invitedBy {
|
if let invitedByPeerId = cachedData.invitedBy {
|
||||||
|
@ -3,6 +3,7 @@ import UIKit
|
|||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Postbox
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import LocalizedPeerData
|
import LocalizedPeerData
|
||||||
@ -14,6 +15,7 @@ import TextNodeWithEntities
|
|||||||
import AnimationCache
|
import AnimationCache
|
||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import PremiumUI
|
||||||
|
|
||||||
private enum ChatReportPeerTitleButton: Equatable {
|
private enum ChatReportPeerTitleButton: Equatable {
|
||||||
case block
|
case block
|
||||||
@ -354,12 +356,17 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
private let emojiSeparatorNode: ASDisplayNode
|
private let emojiSeparatorNode: ASDisplayNode
|
||||||
|
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
|
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||||
|
|
||||||
private var inviteInfoNode: ChatInfoTitlePanelInviteInfoNode?
|
private var inviteInfoNode: ChatInfoTitlePanelInviteInfoNode?
|
||||||
private var peerNearbyInfoNode: ChatInfoTitlePanelPeerNearbyInfoNode?
|
private var peerNearbyInfoNode: ChatInfoTitlePanelPeerNearbyInfoNode?
|
||||||
|
|
||||||
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||||
|
|
||||||
|
private var emojiStatusPackDisposable = MetaDisposable()
|
||||||
|
private var emojiStatusFileId: Int64?
|
||||||
|
private var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>()
|
||||||
|
|
||||||
private var tapGestureRecognizer: UITapGestureRecognizer?
|
private var tapGestureRecognizer: UITapGestureRecognizer?
|
||||||
|
|
||||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) {
|
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) {
|
||||||
@ -391,6 +398,10 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
self.addSubnode(self.closeButton)
|
self.addSubnode(self.closeButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.emojiStatusPackDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
@ -405,27 +416,33 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openPremiumEmojiStatusDemo() {
|
private func openPremiumEmojiStatusDemo() {
|
||||||
guard let navigationController = self.interfaceInteraction?.getNavigationController() else {
|
guard let navigationController = self.interfaceInteraction?.getNavigationController(), let peerId = self.presentationInterfaceState?.chatLocation.peerId, let emojiStatus = self.presentationInterfaceState?.renderedPeer?.peer?.emojiStatus, case let .emoji(fileId) = emojiStatus.content else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.context.isPremium {
|
let source: Signal<PremiumSource, NoError> = self.emojiStatusFileAndPackTitle.get()
|
||||||
let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .animatedEmoji, forceDark: false, dismissed: nil)
|
|> take(1)
|
||||||
navigationController.pushViewController(controller)
|
|> mapToSignal { emojiStatusFileAndPack -> Signal<PremiumSource, NoError> in
|
||||||
|
if let (file, pack) = emojiStatusFileAndPack {
|
||||||
|
return .single(.emojiStatus(peerId, fileId, file, pack))
|
||||||
} else {
|
} else {
|
||||||
var replaceImpl: ((ViewController) -> Void)?
|
return .complete()
|
||||||
let controller = self.context.sharedContext.makePremiumDemoController(context: self.context, subject: .emojiStatus, forceDark: false, action: { [weak self] in
|
}
|
||||||
guard let self else {
|
}
|
||||||
|
|
||||||
|
let _ = (source
|
||||||
|
|> deliverOnMainQueue).startStandalone(next: { [weak self, weak navigationController] source in
|
||||||
|
guard let self, let navigationController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .animatedEmoji, forceDark: false, dismissed: nil)
|
let controller = PremiumIntroScreen(context: self.context, source: source)
|
||||||
replaceImpl?(controller)
|
if let textView = self.emojiStatusTextNode?.view {
|
||||||
}, dismissed: nil)
|
controller.sourceView = textView
|
||||||
replaceImpl = { [weak controller] c in
|
controller.sourceRect = CGRect(origin: .zero, size: CGSize(width: textView.frame.height, height: textView.frame.height))
|
||||||
controller?.replace(with: c)
|
|
||||||
}
|
}
|
||||||
|
controller.containerView = navigationController.view
|
||||||
navigationController.pushViewController(controller)
|
navigationController.pushViewController(controller)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
|
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
|
||||||
@ -436,6 +453,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||||
self.emojiSeparatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
self.emojiSeparatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||||
}
|
}
|
||||||
|
self.presentationInterfaceState = interfaceState
|
||||||
|
|
||||||
var panelHeight: CGFloat = 40.0
|
var panelHeight: CGFloat = 40.0
|
||||||
|
|
||||||
@ -583,11 +601,43 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#if DEBUG
|
if let emojiStatus = emojiStatus, case let .emoji(fileId) = emojiStatus.content {
|
||||||
emojiStatus = PeerEmojiStatus(fileId: 5062172592505356289, expirationDate: nil)
|
if self.emojiStatusFileId != fileId {
|
||||||
#endif*/
|
self.emojiStatusFileId = fileId
|
||||||
|
|
||||||
|
let emojiFileAndPack = self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|
||||||
|
|> mapToSignal { result in
|
||||||
|
if let emojiFile = result.first?.value {
|
||||||
|
for attribute in emojiFile.attributes {
|
||||||
|
if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference {
|
||||||
|
return self.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false)
|
||||||
|
|> filter { result in
|
||||||
|
if case .result = result {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError> in
|
||||||
|
if case let .result(_, items, _) = result {
|
||||||
|
return .single(items.first.flatMap { ($0.file._parse(), result) })
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
self.emojiStatusPackDisposable.set(emojiFileAndPack.startStrict(next: { [weak self] fileAndPackTitle in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.emojiStatusFileAndPackTitle.set(.single(fileAndPackTitle))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
if let emojiStatus = emojiStatus {
|
|
||||||
self.emojiSeparatorNode.isHidden = false
|
self.emojiSeparatorNode.isHidden = false
|
||||||
|
|
||||||
transition.updateFrame(node: self.emojiSeparatorNode, frame: CGRect(origin: CGPoint(x: leftInset + 12.0, y: 40.0), size: CGSize(width: width - leftInset - rightInset - 24.0, height: UIScreenPixel)))
|
transition.updateFrame(node: self.emojiSeparatorNode, frame: CGRect(origin: CGPoint(x: leftInset + 12.0, y: 40.0), size: CGSize(width: width - leftInset - rightInset - 24.0, height: UIScreenPixel)))
|
||||||
|
@ -2883,11 +2883,24 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||||
|
if let state = context.starsContext?.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
|
||||||
|
optionsPromise.set(context.engine.payments.starsTopUpOptions()
|
||||||
|
|> map(Optional.init))
|
||||||
|
}
|
||||||
|
|
||||||
presentTransferAlertImpl = { [weak controller] peer in
|
presentTransferAlertImpl = { [weak controller] peer in
|
||||||
guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _, _) = source else {
|
guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _, _) = source else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let alertController = giftTransferAlertController(context: context, gift: gift, peer: peer, transferStars: transferStars, commit: { [weak controller] in
|
let alertController = giftTransferAlertController(
|
||||||
|
context: context,
|
||||||
|
gift: gift,
|
||||||
|
peer: peer,
|
||||||
|
transferStars: transferStars,
|
||||||
|
navigationController: controller.navigationController as? NavigationController,
|
||||||
|
commit: { [weak controller] in
|
||||||
|
let proceed: (Bool) -> Void = { waitForTopUp in
|
||||||
completion?([peer.id])
|
completion?([peer.id])
|
||||||
|
|
||||||
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||||
@ -2938,7 +2951,34 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
lastController.present(tooltipController, in: .window(.root))
|
lastController.present(tooltipController, in: .window(.root))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if transferStars > 0, let starsContext = context.starsContext, let starsState = starsContext.currentState {
|
||||||
|
if starsState.balance < StarsAmount(value: transferStars, nanos: 0) {
|
||||||
|
let _ = (optionsPromise.get()
|
||||||
|
|> filter { $0 != nil }
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller] options in
|
||||||
|
let purchaseController = context.sharedContext.makeStarsPurchaseScreen(
|
||||||
|
context: context,
|
||||||
|
starsContext: starsContext,
|
||||||
|
options: options ?? [],
|
||||||
|
purpose: .transferStarGift(requiredStars: transferStars),
|
||||||
|
completion: { stars in
|
||||||
|
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||||
|
proceed(true)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
controller?.push(purchaseController)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
proceed(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proceed(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
controller.present(alertController, in: .window(.root))
|
controller.present(alertController, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2232,6 +2232,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString)
|
self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString)
|
||||||
|
|
||||||
controller.isFullscreen = isFullscreen
|
controller.isFullscreen = isFullscreen
|
||||||
|
if isFullscreen {
|
||||||
|
controller.requestAttachmentMenuExpansion()
|
||||||
|
}
|
||||||
|
|
||||||
if let (layout, _) = self.validLayout, case .regular = layout.metrics.widthClass {
|
if let (layout, _) = self.validLayout, case .regular = layout.metrics.widthClass {
|
||||||
if let snapshotView = self.webView?.snapshotView(afterScreenUpdates: false) {
|
if let snapshotView = self.webView?.snapshotView(afterScreenUpdates: false) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"app": "11.8.1",
|
"app": "11.8.2",
|
||||||
"xcode": "16.2",
|
"xcode": "16.2",
|
||||||
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
|
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
|
||||||
"macos": "15"
|
"macos": "15"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user