Merge commit '037380707d44ffddc6bbde28fb4805233840cf76'

# Conflicts:
#	submodules/PremiumUI/Sources/PremiumIntroScreen.swift
#	submodules/TelegramApi/Sources/Api24.swift
#	submodules/TelegramApi/Sources/Api35.swift
#	submodules/TelegramCore/Sources/State/CallSessionManager.swift
#	submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift
#	submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD
#	submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift
This commit is contained in:
Isaac 2024-03-23 00:03:54 +04:00
commit 60a9eae314
62 changed files with 2631 additions and 229 deletions

View File

@ -11649,7 +11649,7 @@ Sorry for the inconvenience.";
"ChatList.AddBirthdayTitle" = "Add your birthday! 🎂";
"ChatList.AddBirthdayText" = "Let your contacts know when you're celebrating.";
"ChatList.BirthdaySingleTitle" = "It's %@ **birthday** today! 🎂";
"ChatList.BirthdaySingleTitle" = "It's %@'s **birthday** today! 🎂";
"ChatList.BirthdaySingleText" = "Gift them Telegram Premium.";
"ChatList.BirthdayMultipleTitle_1" = "%@ contact have **birthday** today! 🎂";
@ -11657,3 +11657,12 @@ Sorry for the inconvenience.";
"ChatList.BirthdayMultipleText" = "Gift them Telegram Premium.";
"ChatList.BirthdayInSettingsInfo" = "You can set your date of birth later in **Settings**.";
"UserInfo.Birthday" = "date of birth";
"UserInfo.BirthdayToday" = "birthday today";
"UserInfo.Age_1" = "%@ year old";
"UserInfo.Age_any" = "%@ years old";
"Business.Links" = "Links to Chat";
"Business.LinksInfo" = "Create links that start a chat with you, suggesting the first message.";

View File

@ -907,7 +907,7 @@ public protocol SharedAccountContext: AnyObject {
var activeAccountContexts: Signal<(primary: AccountContext?, accounts: [(AccountRecordId, AccountContext, Int32)], currentAuth: UnauthorizedAccount?), NoError> { get }
var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> { get }
var presentGlobalController: (ViewController, Any?) -> Void { get }
var presentCrossfadeController: () -> Void { get }
@ -1081,7 +1081,9 @@ public protocol AccountContext: AnyObject {
var animationCache: AnimationCache { get }
var animationRenderer: MultiAnimationRenderer { get }
var animatedEmojiStickers: [String: [StickerPackItem]] { get }
var animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> { get }
var animatedEmojiStickersValue: [String: [StickerPackItem]] { get }
var additionalAnimatedEmojiStickers: Signal<[String: [Int: StickerPackItem]], NoError> { get }
var isPremium: Bool { get }
var userLimits: EngineConfiguration.UserLimits { get }

View File

@ -129,7 +129,7 @@ public final class AdInfoScreen: ViewController {
openUrl?(value)
}
}
textNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5)
textNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2)
if !didAddUrl {
didAddUrl = true

View File

@ -338,7 +338,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
}
textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
self.updatingInputState = false
self.updateTextNodeText(animated: animated)
@ -1020,7 +1020,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
public func chatInputTextNodeDidUpdateText() {
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize)
self.updateSpoiler()
@ -1192,9 +1192,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
textInputNode.textView.isScrollEnabled = false
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
if textInputNode.textView.subviews.count > 1, animated {
let containerView = textInputNode.textView.subviews[1]
@ -1349,7 +1349,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let textFont = Font.regular(baseFontSize)
let accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
let attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
let attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
let range = (attributedText.string as NSString).range(of: "\n")
if range.location != NSNotFound {
@ -1754,7 +1754,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
}
let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
string.replaceCharacters(in: range, with: cleanReplacementString)
self.textInputNode?.attributedText = string
self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0)

View File

@ -844,7 +844,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
var selectedItems = Set<MediaId>()
var topStatusTitle = self.presentationData.strings.PeerStatusSetup_NoTimerTitle
var currentSelection: Int64?
if let peerStatus = self.findTitleView()?.title.peerStatus, case let .emoji(emojiStatus) = peerStatus {
if let emojiStatus = self.chatListHeaderView()?.emojiStatus() {
selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId))
currentSelection = emojiStatus.fileId

View File

@ -157,7 +157,7 @@ public final class ConfettiView: UIView {
dtAndDamping.append((0.0, 1.0))
} else if let slowdownStart = self.slowdownStartTimestamps[i] {
let slowdownDt: Float
let slowdownDuration: Float = 0.5
let slowdownDuration: Float = 0.7
let damping: Float
if currentTime >= slowdownStart && currentTime <= slowdownStart + slowdownDuration {
let slowdownTimestamp: Float = currentTime - slowdownStart

View File

@ -538,6 +538,9 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
var index: Int = 0
for peer in topPeers.prefix(15) {
if peer.isDeleted {
continue
}
existingPeerIds.insert(.peer(peer.id))
let selection: ContactsPeerItemSelection
@ -579,6 +582,9 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
for peerId in peerIds {
if let peer = topPeers.first(where: { $0.id == peerId }) {
if peer.isDeleted {
continue
}
if existingPeerIds.contains(.peer(peer.id)) {
continue
}
@ -625,6 +631,9 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
})
for peer in topPeers.prefix(15) {
if peer.isDeleted {
continue
}
if existingPeerIds.contains(.peer(peer.id)) {
continue
}

View File

@ -375,6 +375,7 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> U
public enum GradientImageDirection {
case vertical
case horizontal
case diagonal
}
public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [UIColor], locations: [CGFloat], direction: GradientImageDirection = .vertical) -> UIImage? {
@ -413,7 +414,21 @@ public func generateGradientFilledCircleImage(diameter: CGFloat, colors: NSArray
let colorSpace = DeviceGraphicsContextSettings.shared.colorSpace
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: direction == .horizontal ? CGPoint(x: size.width, y: 0.0) : CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
let start: CGPoint
let end: CGPoint
switch direction {
case .horizontal:
start = .zero
end = CGPoint(x: size.width, y: 0.0)
case .vertical:
start = .zero
end = CGPoint(x: 0.0, y: size.height)
case .diagonal:
start = CGPoint(x: 0.0, y: 0.0)
end = CGPoint(x: size.width, y: size.height)
}
context.drawLinearGradient(gradient, start: start, end:end, options: CGGradientDrawingOptions())
})
}

View File

@ -135,7 +135,7 @@ public final class ListSectionHeaderNode: ASDisplayNode {
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.validLayout = (size, leftInset, rightInset)
let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height))
self.label.frame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: labelSize)
self.label.frame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: CGSize(width: labelSize.width, height: size.height))
if let actionButton = self.actionButton, let actionButtonLabel = self.actionButtonLabel {
let buttonSize = actionButtonLabel.updateLayout(CGSize(width: size.width, height: size.height))

View File

@ -274,7 +274,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
if let current = context.state.cachedBoostIcon {
boostIcon = current
} else {
boostIcon = generateImage(CGSize(width: 14.0, height: 20.0), rotatedContext: { size, context in
boostIcon = generateImage(CGSize(width: 14.0, height: 20.0), contextGenerator: { size, context in
context.clear(CGRect(origin: .zero, size: size))
if let cgImage = UIImage(bundleImageName: "Premium/BoostChannel")?.cgImage {
context.draw(cgImage, in: CGRect(origin: .zero, size: size), byTiling: false)

View File

@ -457,8 +457,8 @@ public enum PremiumPerk: CaseIterable {
case businessQuickReplies
case businessAwayMessage
case businessChatBots
case businessLinks
case businessIntro
case businessLinks
public static var allCases: [PremiumPerk] {
return [
@ -493,13 +493,10 @@ public enum PremiumPerk: CaseIterable {
.businessHours,
.businessQuickReplies,
.businessGreetingMessage,
.businessLinks,
.businessAwayMessage,
.businessIntro,
.businessChatBots,
.businessLinks
// .emojiStatus,
// .folderTags,
// .stories,
.businessChatBots
]
}
@ -572,10 +569,10 @@ public enum PremiumPerk: CaseIterable {
return "away_message"
case .businessChatBots:
return "business_bots"
case .businessLinks:
return "business_links"
case .businessIntro:
return "business_intro"
case .businessLinks:
return "business_links"
}
}
@ -638,11 +635,10 @@ public enum PremiumPerk: CaseIterable {
return strings.Business_AwayMessages
case .businessChatBots:
return strings.Business_ChatbotsItem
case .businessLinks:
//TODO:localize
return "Links to Chat"
case .businessIntro:
return strings.Business_Intro
case .businessLinks:
return strings.Business_Links
}
}
@ -705,11 +701,10 @@ public enum PremiumPerk: CaseIterable {
return strings.Business_AwayMessagesInfo
case .businessChatBots:
return strings.Business_ChatbotsInfo
case .businessLinks:
//TODO:localize
return "Create links that start a chat with you, suggesting the first message."
case .businessIntro:
return strings.Business_IntroInfo
case .businessLinks:
return strings.Business_LinksInfo
}
}
@ -772,10 +767,10 @@ public enum PremiumPerk: CaseIterable {
return "Premium/BusinessPerk/Away"
case .businessChatBots:
return "Premium/BusinessPerk/Chatbots"
case .businessLinks:
return "Premium/BusinessPerk/ChatLinks"
case .businessIntro:
return "Premium/BusinessPerk/Intro"
case .businessLinks:
return "Premium/BusinessPerk/Links"
}
}
}
@ -810,12 +805,9 @@ struct PremiumIntroConfiguration {
.businessQuickReplies,
.businessGreetingMessage,
.businessAwayMessage,
.businessLinks,
.businessIntro,
.businessChatBots,
.businessLinks
// .emojiStatus,
// .folderTags,
// .stories
.businessChatBots
])
}
@ -846,20 +838,6 @@ struct PremiumIntroConfiguration {
if perks.count < 4 {
perks = PremiumIntroConfiguration.defaultValue.perks
}
#if DEBUG
if !perks.contains(.lastSeen) {
perks.append(.lastSeen)
}
if !perks.contains(.messagePrivacy) {
perks.append(.messagePrivacy)
}
if !perks.contains(.messageTags) {
perks.append(.messageTags)
}
if !perks.contains(.business) {
perks.append(.business)
}
#endif
var businessPerks: [PremiumPerk] = []
if let values = data["business_promo_order"] as? [String] {
@ -2159,8 +2137,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0xbc4395),
UIColor(rgb: 0x9b4fed),
UIColor(rgb: 0x8958ff),
UIColor(rgb: 0x8958ff),
UIColor(rgb: 0x8958ff),
UIColor(rgb: 0x676bff),
UIColor(rgb: 0x007aff)
]
var i = 0

View File

@ -999,6 +999,26 @@ public class PremiumLimitsListScreen: ViewController {
)
)
availableItems[.businessLinks] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.businessLinks,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: videos["business_links"],
decoration: .business
)),
title: strings.Business_Links,
text: strings.Business_LinksInfo,
textColor: textColor
)
)
)
)
if let order = controller.order {
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
let initialIndex: Int

View File

@ -41,6 +41,7 @@ swift_library(
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/Components/SheetComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/QrCodeUI",

View File

@ -1338,8 +1338,10 @@ private func monetizationEntries(
state: ChannelStatsControllerState,
stats: ChannelStats,
data: MonetizationStats,
animatedEmojis: [String: TelegramMediaFile]
animatedEmojis: [String: [StickerPackItem]]
) -> [StatsEntry] {
let diamond = animatedEmojis["💎"]?.first?.file
var entries: [StatsEntry] = []
//TODO:localize
entries.append(.adsHeader(presentationData.theme, "Telegram shares 50% of the revenue from ads displayed in your channel. [Learn More]()"))
@ -1355,10 +1357,10 @@ private func monetizationEntries(
}
entries.append(.adsProceedsTitle(presentationData.theme, "PROCEEDS OVERVIEW"))
entries.append(.adsProceedsOverview(presentationData.theme, data, animatedEmojis["💎"]))
entries.append(.adsProceedsOverview(presentationData.theme, data, diamond))
entries.append(.adsBalanceTitle(presentationData.theme, "AVAILABLE BALANCE"))
entries.append(.adsBalance(presentationData.theme, data, false, animatedEmojis["💎"], state.monetizationAddress))
entries.append(.adsBalance(presentationData.theme, data, false, diamond, state.monetizationAddress))
entries.append(.adsBalanceInfo(presentationData.theme, "We will transfer your balance to the TON wallet address you specify. [Learn More]()"))
entries.append(.adsTransactionsTitle(presentationData.theme, "TRANSACTION HISTORY"))
@ -1373,7 +1375,7 @@ private func monetizationEntries(
}
entries.append(.adsCpmToggle(presentationData.theme, "Switch off Ads", nil))
entries.append(.adsCpm(presentationData.theme, presentationData.strings, 5, animatedEmojis["💎"]))
entries.append(.adsCpm(presentationData.theme, presentationData.strings, 5, diamond))
entries.append(.adsCpmInfo(presentationData.theme, "Switch off ads or set their minimum CPM."))
return entries
@ -1393,7 +1395,7 @@ private func channelStatsControllerEntries(
giveawayAvailable: Bool,
isGroup: Bool,
boostsOnly: Bool,
animatedEmojis: [String: TelegramMediaFile],
animatedEmojis: [String: [StickerPackItem]],
monetizationData: MonetizationStats?
) -> [StatsEntry] {
switch state.section {
@ -1489,31 +1491,6 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
let animatedEmojiStickers = Promise<[String: TelegramMediaFile]>()
animatedEmojiStickers.set(.single([:])
|> then(
context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: TelegramMediaFile] in
var animatedEmojiStickers: [String: TelegramMediaFile] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for item in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.basicEmoji.0] = item.file
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
if animatedEmojiStickers[strippedEmoji] == nil {
animatedEmojiStickers[strippedEmoji] = item.file
}
}
}
default:
break
}
return animatedEmojiStickers
}
)
)
var dismissAllTooltipsImpl: (() -> Void)?
var presentImpl: ((ViewController) -> Void)?
var pushImpl: ((ViewController) -> Void)?
@ -1721,7 +1698,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
boostsContext.state,
giftsContext.state,
longLoadingSignal,
animatedEmojiStickers.get()
context.animatedEmojiStickers
)
|> deliverOnMainQueue
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, longLoading, animatedEmojiStickers -> (ItemListControllerState, (ItemListNodeState, Any)) in
@ -1837,11 +1814,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
section = .boosts
case 2:
section = .monetization
let _ = (animatedEmojiStickers.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak controller] animatedEmojis in
controller?.push(MonetizationIntroScreen(context: context, animatedEmojis: animatedEmojis, openMore: {}))
})
controller?.push(MonetizationIntroScreen(context: context, openMore: {}))
default:
section = .stats
}

View File

@ -21,18 +21,15 @@ private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let animatedEmojis: [String: TelegramMediaFile]
let openMore: () -> Void
let dismiss: () -> Void
init(
context: AccountContext,
animatedEmojis: [String: TelegramMediaFile],
openMore: @escaping () -> Void,
dismiss: @escaping () -> Void
) {
self.context = context
self.animatedEmojis = animatedEmojis
self.openMore = openMore
self.dismiss = dismiss
}
@ -126,7 +123,7 @@ private final class SheetContent: CombinedComponent {
)
let icon = icon.update(
component: BundleIconComponent(name: "Chart/Monetization", tintColor: theme.list.itemCheckColors.foregroundColor),
component: BundleIconComponent(name: "Ads/MonetizationLogo", tintColor: theme.list.itemCheckColors.foregroundColor),
availableSize: CGSize(width: 90, height: 90),
transition: .immediate
)
@ -163,7 +160,7 @@ private final class SheetContent: CombinedComponent {
titleColor: textColor,
text: "Telegram can display ads in your channel.",
textColor: secondaryTextColor,
iconName: "Chart/Ads",
iconName: "Ads/Ads",
iconColor: linkColor
))
)
@ -176,7 +173,7 @@ private final class SheetContent: CombinedComponent {
titleColor: textColor,
text: "You receive 50% of the ad revenue in TON.",
textColor: secondaryTextColor,
iconName: "Chart/Split",
iconName: "Ads/Split",
iconColor: linkColor
))
)
@ -189,7 +186,7 @@ private final class SheetContent: CombinedComponent {
titleColor: textColor,
text: "You can withdraw your TON any time.",
textColor: secondaryTextColor,
iconName: "Chart/Withdrawal",
iconName: "Ads/Withdrawal",
iconColor: linkColor
))
)
@ -209,7 +206,7 @@ private final class SheetContent: CombinedComponent {
let infoTitleString = "What's #TON?"//.replacingOccurrences(of: "#", with: "# ")
let infoTitleAttributedString = NSMutableAttributedString(string: infoTitleString, font: titleFont, textColor: textColor)
let range = (infoTitleAttributedString.string as NSString).range(of: "#")
if range.location != NSNotFound, let emojiFile = component.animatedEmojis["💎"] {
if range.location != NSNotFound, let emojiFile = component.context.animatedEmojiStickersValue["💎"]?.first?.file {
infoTitleAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range)
}
let infoTitle = infoTitle.update(
@ -317,16 +314,13 @@ private final class SheetContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let animatedEmojis: [String: TelegramMediaFile]
let openMore: () -> Void
init(
context: AccountContext,
animatedEmojis: [String: TelegramMediaFile],
openMore: @escaping () -> Void
) {
self.context = context
self.animatedEmojis = animatedEmojis
self.openMore = openMore
}
@ -352,7 +346,6 @@ private final class SheetContainerComponent: CombinedComponent {
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(SheetContent(
context: context.component.context,
animatedEmojis: context.component.animatedEmojis,
openMore: context.component.openMore,
dismiss: {
animateOut.invoke(Action { _ in
@ -421,23 +414,19 @@ private final class SheetContainerComponent: CombinedComponent {
final class MonetizationIntroScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let animatedEmojis: [String: TelegramMediaFile]
private var openMore: (() -> Void)?
init(
context: AccountContext,
animatedEmojis: [String: TelegramMediaFile],
openMore: @escaping () -> Void
) {
self.context = context
self.animatedEmojis = animatedEmojis
self.openMore = openMore
super.init(
context: context,
component: SheetContainerComponent(
context: context,
animatedEmojis: animatedEmojis,
openMore: openMore
),
navigationBarAppearance: .none,

View File

@ -484,7 +484,7 @@ private final class StickerPackContainer: ASDisplayNode {
if let strongSelf = self {
if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
var canEdit = false
if let (info, _, _) = strongSelf.currentStickerPack, info.flags.contains(.isCreator) {
if let (info, _, _) = strongSelf.currentStickerPack, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
canEdit = true
}
@ -955,7 +955,7 @@ private final class StickerPackContainer: ASDisplayNode {
case .none:
buttonColor = self.presentationData.theme.list.itemAccentColor
case let .result(info, _, installed):
if info.flags.contains(.isCreator) {
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
buttonColor = installed ? self.presentationData.theme.list.itemAccentColor : self.presentationData.theme.list.itemCheckColors.foregroundColor
} else {
buttonColor = installed ? self.presentationData.theme.list.itemDestructiveColor : self.presentationData.theme.list.itemCheckColors.foregroundColor
@ -991,7 +991,7 @@ private final class StickerPackContainer: ASDisplayNode {
}
var isEditable = false
if let info = self.currentStickerPack?.0, info.flags.contains(.isCreator) {
if let info = self.currentStickerPack?.0, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
isEditable = true
}
@ -1082,7 +1082,7 @@ private final class StickerPackContainer: ASDisplayNode {
}
})))
if let (info, packItems, _) = self.currentStickerPack, info.flags.contains(.isCreator) {
if let (info, packItems, _) = self.currentStickerPack, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
//TODO:localize
items.append(.separator)
if packItems.count > 0 {
@ -1402,7 +1402,7 @@ private final class StickerPackContainer: ASDisplayNode {
}
self.requestDismiss()
} else if let (info, _, installed) = self.currentStickerPack {
if installed, info.flags.contains(.isCreator) {
if installed, info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
self.updateIsEditing(!self.isEditing)
return
}
@ -1477,7 +1477,7 @@ private final class StickerPackContainer: ASDisplayNode {
if let currentContents = self.currentContents, currentContents.count == 1, let content = currentContents.first, case let .result(info, _, installed) = content {
if installed {
let text: String
if info.flags.contains(.isCreator) {
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
if self.isEditing {
var updated = false
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != self.presentationData.strings.Common_Done {
@ -1656,7 +1656,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.controller?.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
self.controller?.dismiss(animated: true, completion: nil)
case let .result(info, items, installed):
isEditable = info.flags.contains(.isCreator)
isEditable = info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)
self.onReady()
if !items.isEmpty && self.currentStickerPack == nil {
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
@ -1740,7 +1740,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.updateButton(count: count)
}
if info.flags.contains(.isCreator) {
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
entries.append(.add)
}
}
@ -1841,7 +1841,7 @@ private final class StickerPackContainer: ASDisplayNode {
self.currentEntries = entries
if let controller = self.controller {
let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil, isEditable: info.flags.contains(.isCreator), isEditing: self.isEditing)
let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil, isEditable: info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji), isEditing: self.isEditing)
self.enqueueTransaction(transaction)
}
}
@ -1901,7 +1901,7 @@ private final class StickerPackContainer: ASDisplayNode {
actionAreaBottomInset = 2.0
}
}
if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, !info.flags.contains(.isCreator) {
if let (info, _, isInstalled) = self.currentStickerPack, isInstalled, !info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
buttonHeight = 42.0
actionAreaTopInset = 1.0
actionAreaBottomInset = 2.0

View File

@ -1346,7 +1346,7 @@ public extension Api {
return parser(reader)
}
else {
telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found")
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
return nil
}
}

View File

@ -258,6 +258,8 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
if let boostPeer = boostPeer {
result.append(boostPeer.peerId)
}
case .messageActionRequestedPeerSentMe:
break
}
return result

View File

@ -123,6 +123,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo)))
case let .messageActionRequestedPeer(buttonId, peers):
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerIds: peers.map { $0.peerId }))
case let .messageActionRequestedPeerSentMe(buttonId, _):
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerIds: []))
case let .messageActionSetChatWallPaper(flags, wallpaper):
if (flags & (1 << 0)) != 0 {
return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper)))

View File

@ -119,6 +119,9 @@ func managedContactBirthdays(stateManager: AccountStateManager) -> Signal<Never,
for contactBirthday in contactBirthdays {
if case let .contactBirthday(contactId, birthday) = contactBirthday {
let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(contactId))
if peerId == stateManager.accountPeerId {
continue
}
birthdays[peerId] = TelegramBirthday(apiBirthday: birthday)
}
}

View File

@ -280,6 +280,7 @@ public enum PresentationResourceKey: Int32 {
case chatFreeNavigateButtonIcon
case chatFreeShareButtonIcon
case chatFreeCloseButtonIcon
case chatFreeMoreButtonIcon
case chatKeyboardActionButtonMessageIcon
case chatKeyboardActionButtonLinkIcon

View File

@ -1120,6 +1120,20 @@ public struct PresentationResourcesChat {
})
}
public static func chatFreeMoreButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? {
return theme.image(PresentationResourceKey.chatFreeMoreButtonIcon.rawValue, { _ in
return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper).cgColor)
let dotSize = CGSize(width: 3.0 + UIScreenPixel, height: 3.0 + UIScreenPixel)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - dotSize.height) / 2.0)), size: dotSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - dotSize.width) / 2.0), y: floorToScreenPixels((size.height - dotSize.height) / 2.0)), size: dotSize))
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - dotSize.width, y: floorToScreenPixels((size.height - dotSize.height) / 2.0)), size: dotSize))
})
})
}
public static func chatKeyboardActionButtonMessageIconImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatKeyboardActionButtonMessageIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: theme.chat.inputButtonPanel.buttonTextColor)

View File

@ -146,24 +146,13 @@ public func stringForCompactDate(timestamp: Int32, strings: PresentationStrings,
return "\(shortStringForDayOfWeek(strings: strings, day: timeinfo.tm_wday)) \(timeinfo.tm_mday) \(monthAtIndex(Int(timeinfo.tm_mon), strings: strings))"
}
public func stringForCompactBirthday(_ birthday: TelegramBirthday, strings: PresentationStrings) -> String {
public func stringForCompactBirthday(_ birthday: TelegramBirthday, strings: PresentationStrings, showAge: Bool = false) -> String {
var components: [String] = []
components.append("\(birthday.day)")
components.append(monthAtIndex(Int(birthday.month) - 1, strings: strings))
if let year = birthday.year {
components.append("\(year)")
}
return components.joined(separator: " ")
}
public func stringForFullBirthday(_ birthday: TelegramBirthday, strings: PresentationStrings, showAge: Bool = false) -> String {
var components: [String] = []
components.append("\(birthday.day)")
components.append(stringForMonth(strings: strings, month: birthday.month - 1))
if let year = birthday.year {
components.append("\(year)")
if showAge {
var dateComponents = DateComponents()
dateComponents.day = Int(birthday.day)
@ -173,7 +162,7 @@ public func stringForFullBirthday(_ birthday: TelegramBirthday, strings: Present
let calendar = Calendar.current
if let birthDate = calendar.date(from: dateComponents) {
if let age = calendar.dateComponents([.year], from: birthDate, to: Date()).year {
components.append("(\(age))")
components.append("(\(strings.UserInfo_Age(Int32(age))))")
}
}
}
@ -182,6 +171,24 @@ public func stringForFullBirthday(_ birthday: TelegramBirthday, strings: Present
return components.joined(separator: " ")
}
public func ageForBirthday(_ birthday: TelegramBirthday) -> Int? {
guard let year = birthday.year else {
return nil
}
var dateComponents = DateComponents()
dateComponents.day = Int(birthday.day)
dateComponents.month = Int(birthday.month)
dateComponents.year = Int(year)
let calendar = Calendar.current
if let birthDate = calendar.date(from: dateComponents) {
if let age = calendar.dateComponents([.year], from: birthDate, to: Date()).year {
return age
}
}
return nil
}
public enum RelativeTimestampFormatDay {
case today
case yesterday

View File

@ -0,0 +1,36 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AdsInfoScreen",
module_name = "AdsInfoScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/Components/SolidRoundedButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ScrollComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/Components/SheetComponent",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,584 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import Markdown
import TextFormat
import TelegramPresentationData
import ViewControllerComponent
import ScrollComponent
import BundleIconComponent
import BalancedTextComponent
import MultilineTextComponent
import SolidRoundedButtonComponent
import AccountContext
import ScrollComponent
private final class ScrollContent: CombinedComponent {
typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment)
let context: AccountContext
let openMore: () -> Void
let dismiss: () -> Void
init(
context: AccountContext,
openMore: @escaping () -> Void,
dismiss: @escaping () -> Void
) {
self.context = context
self.openMore = openMore
self.dismiss = dismiss
}
static func ==(lhs: ScrollContent, rhs: ScrollContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
final class State: ComponentState {
var cachedIconImage: (UIImage, PresentationTheme)?
var cachedChevronImage: (UIImage, PresentationTheme)?
let playOnce = ActionSlot<Void>()
private var didPlayAnimation = false
func playAnimationIfNeeded() {
guard !self.didPlayAnimation else {
return
}
self.didPlayAnimation = true
self.playOnce.invoke(Void())
}
}
func makeState() -> State {
return State()
}
static var body: Body {
let iconBackground = Child(Image.self)
let icon = Child(BundleIconComponent.self)
let title = Child(BalancedTextComponent.self)
let list = Child(List<Empty>.self)
let actionButton = Child(SolidRoundedButtonComponent.self)
let infoBackground = Child(RoundedRectangle.self)
let infoTitle = Child(MultilineTextComponent.self)
let infoText = Child(MultilineTextComponent.self)
return { context in
// let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let component = context.component
let state = context.state
let theme = environment.theme
// let strings = environment.strings
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
let titleFont = Font.semibold(20.0)
let textFont = Font.regular(15.0)
let textColor = theme.actionSheet.primaryTextColor
let secondaryTextColor = theme.actionSheet.secondaryTextColor
let linkColor = theme.actionSheet.controlAccentColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
//TODO:localize
let spacing: CGFloat = 16.0
var contentSize = CGSize(width: context.availableSize.width, height: 32.0)
let iconSize = CGSize(width: 90.0, height: 90.0)
let gradientImage: UIImage
if let (current, currentTheme) = state.cachedIconImage, currentTheme === theme {
gradientImage = current
} else {
gradientImage = generateGradientFilledCircleImage(diameter: iconSize.width, colors: [
UIColor(rgb: 0x6e91ff).cgColor,
UIColor(rgb: 0x9472ff).cgColor,
UIColor(rgb: 0xcc6cdd).cgColor
], direction: .diagonal)!
context.state.cachedIconImage = (gradientImage, theme)
}
let iconBackground = iconBackground.update(
component: Image(image: gradientImage),
availableSize: iconSize,
transition: .immediate
)
context.add(iconBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))
)
let icon = icon.update(
component: BundleIconComponent(name: "Ads/AdsLogo", tintColor: theme.list.itemCheckColors.foregroundColor),
availableSize: CGSize(width: 90, height: 90),
transition: .immediate
)
context.add(icon
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))
)
contentSize.height += iconSize.height
contentSize.height += spacing + 5.0
let title = title.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "About These Ads", font: titleFont, textColor: textColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.1
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
)
contentSize.height += title.size.height
contentSize.height += spacing
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: "respect",
component: AnyComponent(ParagraphComponent(
title: "Respect Your Privacy",
titleColor: textColor,
text: "Ads on Telegram do not use your personal information and are based on the channel in which you see them.",
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Ads/Privacy",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "split",
component: AnyComponent(ParagraphComponent(
title: "Help the Channel Creator",
titleColor: textColor,
text: "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.",
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Ads/Split",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "ads",
component: AnyComponent(ParagraphComponent(
title: "Can Be Removed",
titleColor: textColor,
text: "You can turn off ads by subscribing to [Telegram Premium](), and Level 30 channels can remove them for their subscribers.",
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/BoostPerk/NoAds",
iconColor: linkColor
))
)
)
let list = list.update(
component: List(items),
availableSize: CGSize(width: context.availableSize.width - sideInset, height: 10000.0),
transition: context.transition
)
context.add(list
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0))
)
contentSize.height += list.size.height
contentSize.height += spacing - 9.0
let infoTitleAttributedString = NSMutableAttributedString(string: "Can I Launch an Ad?", font: titleFont, textColor: textColor)
let infoTitle = infoTitle.update(
component: MultilineTextComponent(
text: .plain(infoTitleAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.4, height: context.availableSize.height),
transition: .immediate
)
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
}
let infoString = "Anyone can create an ad to display in this channel with minimal budgets. Check out the Telegram Ad Platform for details. [Learn More >]()"
let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
infoAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: infoAttributedString.string))
}
let infoText = infoText.update(
component: MultilineTextComponent(
text: .plain(infoAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - (textSideInset + sideInset - 2.0) * 2.0, height: context.availableSize.height),
transition: .immediate
)
let infoPadding: CGFloat = 17.0
let infoSpacing: CGFloat = 12.0
let totalInfoHeight = infoPadding + infoTitle.size.height + infoSpacing + infoText.size.height + infoPadding
let infoBackground = infoBackground.update(
component: RoundedRectangle(
color: theme.list.blocksBackgroundColor,
cornerRadius: 10.0
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: totalInfoHeight),
transition: .immediate
)
context.add(infoBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoBackground.size.height / 2.0))
)
contentSize.height += infoPadding
context.add(infoTitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoTitle.size.height / 2.0))
)
contentSize.height += infoTitle.size.height
contentSize.height += infoSpacing
context.add(infoText
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoText.size.height / 2.0))
)
contentSize.height += infoText.size.height
contentSize.height += infoPadding
contentSize.height += spacing
let actionButton = actionButton.update(
component: SolidRoundedButtonComponent(
title: "Understood",
theme: SolidRoundedButtonComponent.Theme(
backgroundColor: theme.list.itemCheckColors.fillColor,
backgroundColors: [],
foregroundColor: theme.list.itemCheckColors.foregroundColor
),
font: .bold,
fontSize: 17.0,
height: 50.0,
cornerRadius: 10.0,
gloss: false,
iconName: nil,
animationName: nil,
iconPosition: .left,
action: {
component.dismiss()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition
)
context.add(actionButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0))
)
contentSize.height += actionButton.size.height
contentSize.height += 22.0
contentSize.height += environment.safeInsets.bottom
state.playAnimationIfNeeded()
return contentSize
}
}
}
private final class ContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let openMore: () -> Void
init(
context: AccountContext,
openMore: @escaping () -> Void
) {
self.context = context
self.openMore = openMore
}
static func ==(lhs: ContainerComponent, rhs: ContainerComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
static var body: Body {
let background = Child(Rectangle.self)
let scroll = Child(ScrollComponent<ViewControllerComponentContainer.Environment>.self)
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let background = background.update(
component: Rectangle(color: environment.theme.list.plainBackgroundColor),
environment: {},
availableSize: context.availableSize,
transition: context.transition
)
context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
let scroll = scroll.update(
component: ScrollComponent<EnvironmentType>(
content: AnyComponent(ScrollContent(
context: context.component.context,
openMore: context.component.openMore,
dismiss: {
controller()?.dismiss()
}
)),
contentInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0),
contentOffsetUpdated: { topContentOffset, bottomContentOffset in
// state?.topContentOffset = topContentOffset
// state?.bottomContentOffset = bottomContentOffset
// Queue.mainQueue().justDispatch {
// state?.updated(transition: .immediate)
// }
},
contentOffsetWillCommit: { targetContentOffset in
}
),
environment: { environment },
availableSize: context.availableSize,
transition: context.transition
)
// let sheet = sheet.update(
// component: SheetComponent<EnvironmentType>(
// content: AnyComponent<EnvironmentType>(ScrollContent(
// context: context.component.context,
// openMore: context.component.openMore,
// dismiss: {
// animateOut.invoke(Action { _ in
// if let controller = controller() {
// controller.dismiss(completion: nil)
// }
// })
// }
// )),
// backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
// followContentSizeChanges: true,
// externalState: sheetExternalState,
// animateOut: animateOut
// ),
// environment: {
// environment
// SheetComponentEnvironment(
// isDisplaying: environment.value.isVisible,
// isCentered: environment.metrics.widthClass == .regular,
// hasInputHeight: !environment.inputHeight.isZero,
// regularMetricsSize: CGSize(width: 430.0, height: 900.0),
// dismiss: { animated in
// if animated {
// animateOut.invoke(Action { _ in
// if let controller = controller() {
// controller.dismiss(completion: nil)
// }
// })
// } else {
// if let controller = controller() {
// controller.dismiss(completion: nil)
// }
// }
// }
// )
// },
// availableSize: context.availableSize,
// transition: context.transition
// )
context.add(scroll
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
return context.availableSize
}
}
}
public final class AdsInfoScreen: ViewControllerComponentContainer {
private let context: AccountContext
public init(
context: AccountContext
) {
self.context = context
super.init(
context: context,
component: ContainerComponent(
context: context,
openMore: {}
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.navigationPresentation = .modal
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
private final class ParagraphComponent: CombinedComponent {
let title: String
let titleColor: UIColor
let text: String
let textColor: UIColor
let accentColor: UIColor
let iconName: String
let iconColor: UIColor
public init(
title: String,
titleColor: UIColor,
text: String,
textColor: UIColor,
accentColor: UIColor,
iconName: String,
iconColor: UIColor
) {
self.title = title
self.titleColor = titleColor
self.text = text
self.textColor = textColor
self.accentColor = accentColor
self.iconName = iconName
self.iconColor = iconColor
}
static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.titleColor != rhs.titleColor {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.accentColor != rhs.accentColor {
return false
}
if lhs.iconName != rhs.iconName {
return false
}
if lhs.iconColor != rhs.iconColor {
return false
}
return true
}
static var body: Body {
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let icon = Child(BundleIconComponent.self)
return { context in
let component = context.component
let leftInset: CGFloat = 40.0
let rightInset: CGFloat = 32.0
let textSideInset: CGFloat = leftInset + 8.0
let spacing: CGFloat = 5.0
let textTopInset: CGFloat = 9.0
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: component.title,
font: Font.semibold(15.0),
textColor: component.titleColor,
paragraphAlignment: .natural
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = component.textColor
let accentColor = component.accentColor
let markdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
linkAttribute: { _ in
return nil
}
)
let text = text.update(
component: MultilineTextComponent(
text: .markdown(text: component.text, attributes: markdownAttributes),
horizontalAlignment: .natural,
maximumNumberOfLines: 0,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
transition: .immediate
)
let icon = icon.update(
component: BundleIconComponent(
name: component.iconName,
tintColor: component.iconColor
),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
)
context.add(text
.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))
)
context.add(icon
.position(CGPoint(x: 23.0, y: textTopInset + 18.0))
)
return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0)
}
}
}

View File

@ -0,0 +1,35 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AdsReportScreen",
module_name = "AdsReportScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/Components/SolidRoundedButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/Components/SheetComponent",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,583 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import Markdown
import TextFormat
import TelegramPresentationData
import ViewControllerComponent
import SheetComponent
import BundleIconComponent
import BalancedTextComponent
import MultilineTextComponent
import SolidRoundedButtonComponent
import AccountContext
private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let animatedEmojis: [String: TelegramMediaFile]
let openMore: () -> Void
let dismiss: () -> Void
init(
context: AccountContext,
animatedEmojis: [String: TelegramMediaFile],
openMore: @escaping () -> Void,
dismiss: @escaping () -> Void
) {
self.context = context
self.animatedEmojis = animatedEmojis
self.openMore = openMore
self.dismiss = dismiss
}
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
final class State: ComponentState {
var cachedIconImage: (UIImage, PresentationTheme)?
var cachedChevronImage: (UIImage, PresentationTheme)?
let playOnce = ActionSlot<Void>()
private var didPlayAnimation = false
func playAnimationIfNeeded() {
guard !self.didPlayAnimation else {
return
}
self.didPlayAnimation = true
self.playOnce.invoke(Void())
}
}
func makeState() -> State {
return State()
}
static var body: Body {
let iconBackground = Child(Image.self)
let icon = Child(BundleIconComponent.self)
let title = Child(BalancedTextComponent.self)
let list = Child(List<Empty>.self)
let actionButton = Child(SolidRoundedButtonComponent.self)
let infoBackground = Child(RoundedRectangle.self)
let infoTitle = Child(MultilineTextComponent.self)
let infoText = Child(MultilineTextComponent.self)
return { context in
let environment = context.environment[EnvironmentType.self]
let component = context.component
let state = context.state
let theme = environment.theme
// let strings = environment.strings
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 30.0 + environment.safeInsets.left
let titleFont = Font.semibold(20.0)
let textFont = Font.regular(15.0)
let textColor = theme.actionSheet.primaryTextColor
let secondaryTextColor = theme.actionSheet.secondaryTextColor
let linkColor = theme.actionSheet.controlAccentColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
//TODO:localize
let spacing: CGFloat = 16.0
var contentSize = CGSize(width: context.availableSize.width, height: 32.0)
let iconSize = CGSize(width: 90.0, height: 90.0)
let gradientImage: UIImage
if let (current, currentTheme) = state.cachedIconImage, currentTheme === theme {
gradientImage = current
} else {
gradientImage = generateGradientFilledCircleImage(diameter: iconSize.width, colors: [
UIColor(rgb: 0x4bbb45).cgColor,
UIColor(rgb: 0x9ad164).cgColor
])!
context.state.cachedIconImage = (gradientImage, theme)
}
let iconBackground = iconBackground.update(
component: Image(image: gradientImage),
availableSize: iconSize,
transition: .immediate
)
context.add(iconBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))
)
let icon = icon.update(
component: BundleIconComponent(name: "Chart/Monetization", tintColor: theme.list.itemCheckColors.foregroundColor),
availableSize: CGSize(width: 90, height: 90),
transition: .immediate
)
context.add(icon
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))
)
contentSize.height += iconSize.height
contentSize.height += spacing + 5.0
let title = title.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: "Earn From Your Channel", font: titleFont, textColor: textColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.1
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
)
contentSize.height += title.size.height
contentSize.height += spacing
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: "ads",
component: AnyComponent(ParagraphComponent(
title: "Telegram Ads",
titleColor: textColor,
text: "Telegram can display ads in your channel.",
textColor: secondaryTextColor,
iconName: "Chart/Ads",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "split",
component: AnyComponent(ParagraphComponent(
title: "50:50 Revenue Split",
titleColor: textColor,
text: "You receive 50% of the ad revenue in TON.",
textColor: secondaryTextColor,
iconName: "Chart/Split",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "withdrawal",
component: AnyComponent(ParagraphComponent(
title: "Flexible Withdrawals",
titleColor: textColor,
text: "You can withdraw your TON any time.",
textColor: secondaryTextColor,
iconName: "Chart/Withdrawal",
iconColor: linkColor
))
)
)
let list = list.update(
component: List(items),
availableSize: CGSize(width: context.availableSize.width - sideInset, height: 10000.0),
transition: context.transition
)
context.add(list
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0))
)
contentSize.height += list.size.height
contentSize.height += spacing - 9.0
let infoTitleString = "What's #TON?"//.replacingOccurrences(of: "#", with: "# ")
let infoTitleAttributedString = NSMutableAttributedString(string: infoTitleString, font: titleFont, textColor: textColor)
let infoTitle = infoTitle.update(
component: MultilineTextComponent(
text: .plain(infoTitleAttributedString),
horizontalAlignment: .natural,
maximumNumberOfLines: 0,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
transition: .immediate
)
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
}
let infoString = "TON is a blockchain platform and cryptocurrency that Telegram uses for its record scalability and ultra low commissions on transactions.\n[Learn More >]()"
let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
infoAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: infoAttributedString.string))
}
let infoText = infoText.update(
component: MultilineTextComponent(
text: .plain(infoAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - (textSideInset + sideInset - 2.0) * 2.0, height: context.availableSize.height),
transition: .immediate
)
let infoPadding: CGFloat = 17.0
let infoSpacing: CGFloat = 12.0
let totalInfoHeight = infoPadding + infoTitle.size.height + infoSpacing + infoText.size.height + infoPadding
let infoBackground = infoBackground.update(
component: RoundedRectangle(
color: theme.list.blocksBackgroundColor,
cornerRadius: 10.0
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: totalInfoHeight),
transition: .immediate
)
context.add(infoBackground
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoBackground.size.height / 2.0))
)
contentSize.height += infoPadding
context.add(infoTitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoTitle.size.height / 2.0))
)
contentSize.height += infoTitle.size.height
contentSize.height += infoSpacing
context.add(infoText
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoText.size.height / 2.0))
)
contentSize.height += infoText.size.height
contentSize.height += infoPadding
contentSize.height += spacing
let actionButton = actionButton.update(
component: SolidRoundedButtonComponent(
title: "Understood",
theme: SolidRoundedButtonComponent.Theme(
backgroundColor: theme.list.itemCheckColors.fillColor,
backgroundColors: [],
foregroundColor: theme.list.itemCheckColors.foregroundColor
),
font: .bold,
fontSize: 17.0,
height: 50.0,
cornerRadius: 10.0,
gloss: false,
iconName: nil,
animationName: nil,
iconPosition: .left,
action: {
component.dismiss()
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
transition: context.transition
)
context.add(actionButton
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + actionButton.size.height / 2.0))
)
contentSize.height += actionButton.size.height
contentSize.height += 22.0
contentSize.height += environment.safeInsets.bottom
state.playAnimationIfNeeded()
return contentSize
}
}
}
private final class SheetContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let animatedEmojis: [String: TelegramMediaFile]
let openMore: () -> Void
init(
context: AccountContext,
animatedEmojis: [String: TelegramMediaFile],
openMore: @escaping () -> Void
) {
self.context = context
self.animatedEmojis = animatedEmojis
self.openMore = openMore
}
static func ==(lhs: SheetContainerComponent, rhs: SheetContainerComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
return true
}
static var body: Body {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let sheet = sheet.update(
component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(SheetContent(
context: context.component.context,
animatedEmojis: context.component.animatedEmojis,
openMore: context.component.openMore,
dismiss: {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
}
)),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true,
externalState: sheetExternalState,
animateOut: animateOut
),
environment: {
environment
SheetComponentEnvironment(
isDisplaying: environment.value.isVisible,
isCentered: environment.metrics.widthClass == .regular,
hasInputHeight: !environment.inputHeight.isZero,
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in
if animated {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
} else {
if let controller = controller() {
controller.dismiss(completion: nil)
}
}
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
let layout = ContainerViewLayout(
size: context.availableSize,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0),
safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
additionalInsets: .zero,
statusBarHeight: environment.statusBarHeight,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
)
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
}
return context.availableSize
}
}
}
final class AdsReportScreen: ViewControllerComponentContainer {
private let context: AccountContext
private let animatedEmojis: [String: TelegramMediaFile]
private var openMore: (() -> Void)?
init(
context: AccountContext,
animatedEmojis: [String: TelegramMediaFile],
openMore: @escaping () -> Void
) {
self.context = context
self.animatedEmojis = animatedEmojis
self.openMore = openMore
super.init(
context: context,
component: SheetContainerComponent(
context: context,
animatedEmojis: animatedEmojis,
openMore: openMore
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.navigationPresentation = .flatModal
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.disablesInteractiveModalDismiss = true
}
func dismissAnimated() {
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated()
}
}
}
private final class ParagraphComponent: CombinedComponent {
let title: String
let titleColor: UIColor
let text: String
let textColor: UIColor
let iconName: String
let iconColor: UIColor
public init(
title: String,
titleColor: UIColor,
text: String,
textColor: UIColor,
iconName: String,
iconColor: UIColor
) {
self.title = title
self.titleColor = titleColor
self.text = text
self.textColor = textColor
self.iconName = iconName
self.iconColor = iconColor
}
static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.titleColor != rhs.titleColor {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.iconName != rhs.iconName {
return false
}
if lhs.iconColor != rhs.iconColor {
return false
}
return true
}
static var body: Body {
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let icon = Child(BundleIconComponent.self)
return { context in
let component = context.component
let leftInset: CGFloat = 64.0
let rightInset: CGFloat = 32.0
let textSideInset: CGFloat = leftInset + 8.0
let spacing: CGFloat = 5.0
let textTopInset: CGFloat = 9.0
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: component.title,
font: Font.semibold(15.0),
textColor: component.titleColor,
paragraphAlignment: .natural
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = component.textColor
let markdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: textColor),
linkAttribute: { _ in
return nil
}
)
let text = text.update(
component: MultilineTextComponent(
text: .markdown(text: component.text, attributes: markdownAttributes),
horizontalAlignment: .natural,
maximumNumberOfLines: 0,
lineSpacing: 0.2
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
transition: .immediate
)
let icon = icon.update(
component: BundleIconComponent(
name: component.iconName,
tintColor: component.iconColor
),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
)
context.add(text
.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))
)
context.add(icon
.position(CGPoint(x: 47.0, y: textTopInset + 18.0))
)
return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0)
}
}
}

View File

@ -1431,7 +1431,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
strongSelf.shareButtonNode = updatedShareButtonNode
strongSelf.addSubnode(updatedShareButtonNode)
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
updatedShareButtonNode.pressed = { [weak strongSelf] in
strongSelf?.shareButtonPressed()
}
}
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account)
updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: !incoming ? updatedImageFrame.minX - buttonSize.width - 6.0 : updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - buttonSize.height - 4.0 + imageBottomPadding), size: buttonSize)
@ -2491,7 +2493,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
return shareButtonNode.view
return shareButtonNode.view.hitTest(self.view.convert(point, to: shareButtonNode.view), with: event)
}
if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) {
return result

View File

@ -3908,7 +3908,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let shareButtonNode = ChatMessageShareButton()
strongSelf.shareButtonNode = shareButtonNode
strongSelf.insertSubnode(shareButtonNode, belowSubnode: strongSelf.messageAccessibilityArea)
shareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
shareButtonNode.pressed = { [weak strongSelf] in
strongSelf?.shareButtonPressed()
}
shareButtonNode.morePressed = { [weak strongSelf] in
strongSelf?.openMessageContextMenu()
}
}
} else if let shareButtonNode = strongSelf.shareButtonNode {
strongSelf.shareButtonNode = nil
@ -4749,7 +4754,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
return shareButtonNode.view
return shareButtonNode.view.hitTest(self.view.convert(point, to: shareButtonNode.view), with: event)
}
if let selectionNode = self.selectionNode {

View File

@ -690,7 +690,9 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
}
strongSelf.shareButtonNode = updatedShareButtonNode
strongSelf.addSubnode(updatedShareButtonNode)
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
updatedShareButtonNode.pressed = { [weak strongSelf] in
strongSelf?.shareButtonPressed()
}
}
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account)
updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: min(params.width - buttonSize.width - 8.0, videoFrame.maxX - 7.0), y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize)
@ -1170,7 +1172,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
return shareButtonNode.view
return shareButtonNode.view.hitTest(self.view.convert(point, to: shareButtonNode.view), with: event)
}
if !self.bounds.contains(point) {
return nil

View File

@ -10,12 +10,18 @@ import Postbox
import WallpaperBackgroundNode
import ChatMessageItemCommon
public class ChatMessageShareButton: HighlightableButtonNode {
public class ChatMessageShareButton: ASDisplayNode {
private var backgroundContent: WallpaperBubbleBackgroundNode?
private var backgroundBlurView: PortalView?
private let iconNode: ASImageNode
private var iconOffset = CGPoint()
private let topButton: HighlightTrackingButtonNode
private let topIconNode: ASImageNode
private var topIconOffset = CGPoint()
private var bottomButton: HighlightTrackingButtonNode?
private var bottomIconNode: ASImageNode?
private var separatorNode: ASDisplayNode?
private var theme: PresentationTheme?
private var isReplies: Bool = false
@ -24,19 +30,51 @@ public class ChatMessageShareButton: HighlightableButtonNode {
private var absolutePosition: (CGRect, CGSize)?
public init() {
self.iconNode = ASImageNode()
public var pressed: (() -> Void)?
public var morePressed: (() -> Void)?
override public init() {
self.topButton = HighlightTrackingButtonNode()
self.topIconNode = ASImageNode()
self.topIconNode.displaysAsynchronously = false
super.init(pointerStyle: nil)
super.init()
self.allowsGroupOpacity = true
self.addSubnode(self.iconNode)
self.addSubnode(self.topIconNode)
self.addSubnode(self.topButton)
self.topButton.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.topButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.topIconNode.layer.removeAnimation(forKey: "opacity")
self.topIconNode.alpha = 0.4
self.textNode?.layer.removeAnimation(forKey: "opacity")
self.textNode?.alpha = 0.4
} else {
self.topIconNode.alpha = 1.0
self.topIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.textNode?.alpha = 1.0
self.textNode?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func buttonPressed() {
self.pressed?()
}
@objc private func moreButtonPressed() {
self.morePressed?()
}
public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize {
var isReplies = false
@ -64,10 +102,13 @@ public class ChatMessageShareButton: HighlightableButtonNode {
self.isReplies = isReplies
var updatedIconImage: UIImage?
var updatedBottomIconImage: UIImage?
var updatedIconOffset = CGPoint()
if message.adAttribute != nil {
updatedIconImage = PresentationResourcesChat.chatFreeCloseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: UIScreenPixel)
updatedBottomIconImage = PresentationResourcesChat.chatFreeMoreButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
} else if case .pinnedMessages = subject {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)
@ -79,11 +120,62 @@ public class ChatMessageShareButton: HighlightableButtonNode {
} else {
updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
}
//self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate)
self.iconNode.image = updatedIconImage
self.iconOffset = updatedIconOffset
self.topIconNode.image = updatedIconImage
self.topIconOffset = updatedIconOffset
if let updatedBottomIconImage {
let bottomButton: HighlightTrackingButtonNode
let bottomIconNode: ASImageNode
let separatorNode: ASDisplayNode
if let currentButton = self.bottomButton, let currentIcon = self.bottomIconNode, let currentSeparator = self.separatorNode {
bottomButton = currentButton
bottomIconNode = currentIcon
separatorNode = currentSeparator
} else {
bottomButton = HighlightTrackingButtonNode()
bottomButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
self.bottomButton = bottomButton
bottomIconNode = ASImageNode()
bottomIconNode.displaysAsynchronously = false
self.bottomIconNode = bottomIconNode
bottomButton.highligthedChanged = { [weak self] highlighted in
guard let self, let bottomIconNode = self.bottomIconNode else {
return
}
if highlighted {
bottomIconNode.layer.removeAnimation(forKey: "opacity")
bottomIconNode.alpha = 0.4
} else {
bottomIconNode.alpha = 1.0
bottomIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
separatorNode = ASDisplayNode()
self.separatorNode = separatorNode
self.addSubnode(separatorNode)
self.addSubnode(bottomIconNode)
self.addSubnode(bottomButton)
}
separatorNode.backgroundColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper).withAlphaComponent(0.15)
bottomIconNode.image = updatedBottomIconImage
} else {
self.bottomButton?.removeFromSupernode()
self.bottomButton = nil
self.bottomIconNode?.removeFromSupernode()
self.bottomIconNode = nil
self.separatorNode?.removeFromSupernode()
self.separatorNode = nil
}
}
var size = CGSize(width: 30.0, height: 30.0)
if message.adAttribute != nil {
size.height += 30.0
}
var offsetIcon = false
if isReplies, replyCount > 0 {
offsetIcon = true
@ -129,13 +221,18 @@ public class ChatMessageShareButton: HighlightableButtonNode {
backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size)
backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0
}
if let image = self.topIconNode.image {
self.topIconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.topIconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.topIconOffset.y), size: image.size)
}
self.topButton.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.width))
//self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
//self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0, transition: .immediate)
if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.iconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.iconOffset.y), size: image.size)
if let bottomIconNode = self.bottomIconNode, let bottomButton = self.bottomButton, let bottomImage = bottomIconNode.image {
bottomIconNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - bottomImage.size.width) / 2.0), y: size.height - size.width + floorToScreenPixels((size.width - bottomImage.size.height) / 2.0)), size: bottomImage.size)
bottomButton.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width))
}
self.separatorNode?.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width, height: 1.0 - UIScreenPixel))
if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {

View File

@ -986,7 +986,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
}
strongSelf.shareButtonNode = updatedShareButtonNode
strongSelf.addSubnode(updatedShareButtonNode)
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
updatedShareButtonNode.pressed = { [weak strongSelf] in
strongSelf?.shareButtonPressed()
}
}
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account)
let shareButtonFrame = CGRect(origin: CGPoint(x: baseShareButtonFrame.minX, y: baseShareButtonFrame.maxY - buttonSize.height), size: buttonSize)
@ -1600,7 +1602,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
return shareButtonNode.view
return shareButtonNode.view.hitTest(self.view.convert(point, to: shareButtonNode.view), with: event)
}
if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) {
return result

View File

@ -1090,6 +1090,18 @@ public final class ChatListHeaderComponent: Component {
public func findTitleView() -> ChatListTitleView? {
return self.primaryContentView?.chatListTitleView
}
public func emojiStatus() -> PeerEmojiStatus? {
guard let component = self.component else {
return nil
}
if let _ = component.storySubscriptions, let primaryContent = component.primaryContent, let chatListTitle = primaryContent.chatListTitle, let peerStatus = chatListTitle.peerStatus, case let .emoji(emojiStatus) = peerStatus {
return emojiStatus
} else if let peerStatus = self.findTitleView()?.title.peerStatus, case let .emoji(emojiStatus) = peerStatus {
return emojiStatus
}
return nil
}
}
public func makeView() -> View {

View File

@ -143,6 +143,7 @@ swift_library(
"//submodules/TelegramUI/Components/TextLoadingEffect",
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
"//submodules/ConfettiEffect",
],
visibility = [
"//visibility:public",

View File

@ -2,6 +2,7 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
import AccountContext
import TelegramCore
import TextFormat
import UIKit
import AppBundle
@ -9,6 +10,7 @@ import TelegramStringFormatting
import ContextUI
import SwiftSignalKit
import TextLoadingEffect
import EmojiTextAttachmentView
enum PeerInfoScreenLabeledValueTextColor {
case primary
@ -20,8 +22,13 @@ enum PeerInfoScreenLabeledValueTextBehavior: Equatable {
case multiLine(maxLines: Int, enabledEntities: EnabledEntityTypes)
}
enum PeerInfoScreenLabeledValueLeftIcon {
case birthday
}
enum PeerInfoScreenLabeledValueIcon {
case qrCode
case premiumGift
}
private struct TextLinkItemSource: Equatable {
@ -43,11 +50,13 @@ private struct TextLinkItemSource: Equatable {
final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
let id: AnyHashable
let context: AccountContext?
let label: String
let text: String
let additionalText: String?
let textColor: PeerInfoScreenLabeledValueTextColor
let textBehavior: PeerInfoScreenLabeledValueTextBehavior
let leftIcon: PeerInfoScreenLabeledValueLeftIcon?
let icon: PeerInfoScreenLabeledValueIcon?
let action: ((ASDisplayNode, Promise<Bool>?) -> Void)?
let longTapAction: ((ASDisplayNode) -> Void)?
@ -58,11 +67,13 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
init(
id: AnyHashable,
context: AccountContext? = nil,
label: String,
text: String,
additionalText: String? = nil,
textColor: PeerInfoScreenLabeledValueTextColor = .primary,
textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine,
leftIcon: PeerInfoScreenLabeledValueLeftIcon? = nil,
icon: PeerInfoScreenLabeledValueIcon? = nil,
action: ((ASDisplayNode, Promise<Bool>?) -> Void)?,
longTapAction: ((ASDisplayNode) -> Void)? = nil,
@ -72,11 +83,13 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
requestLayout: @escaping () -> Void
) {
self.id = id
self.context = context
self.label = label
self.text = text
self.additionalText = additionalText
self.textColor = textColor
self.textBehavior = textBehavior
self.leftIcon = leftIcon
self.icon = icon
self.action = action
self.longTapAction = longTapAction
@ -131,6 +144,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
private let iconNode: ASImageNode
private let iconButtonNode: HighlightTrackingButtonNode
private var animatedEmojiLayer: InlineStickerItemLayer?
private var linkHighlightingNode: LinkHighlightingNode?
private let activateArea: AccessibilityAreaNode
@ -445,6 +460,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
switch icon {
case .qrCode:
iconImage = UIImage(bundleImageName: "Settings/QrIcon")
case .premiumGift:
iconImage = UIImage(bundleImageName: "Premium/Gift")
}
self.iconNode.image = generateTintedImage(image: iconImage, color: presentationData.theme.list.itemAccentColor)
self.iconNode.isHidden = false
@ -555,13 +572,43 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
topOffset += labelSize.height + 3.0
height += labelSize.height + 3.0
}
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: textSize)
var textFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: textSize)
if textSize.height > 0.0 {
topOffset += textSize.height + 3.0
height += textSize.height
}
let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: additionalTextSize)
if let context = item.context, let leftIcon = item.leftIcon {
var file: TelegramMediaFile?
switch leftIcon {
case .birthday:
file = context.animatedEmojiStickersValue["🎂"]?.first?.file
}
if let file {
let itemSize = floorToScreenPixels(17.0)
var itemFrame = CGRect(origin: CGPoint(x: textFrame.minX + itemSize / 2.0, y: textFrame.midY), size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
let itemLayer: InlineStickerItemLayer
if let current = self.animatedEmojiLayer {
itemLayer = current
} else {
let pointSize = floor(itemSize * 1.3)
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: true, emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file, custom: nil), file: file, cache: context.animationCache, renderer: context.animationRenderer, placeholderColor: presentationData.theme.list.mediaPlaceholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: nil)
self.animatedEmojiLayer = itemLayer
self.layer.addSublayer(itemLayer)
itemLayer.isVisibleForAnimations = true
}
itemLayer.frame = itemFrame
textFrame.origin.x += 20.0
}
}
let expandFrame = CGRect(origin: CGPoint(x: width - safeInsets.right - expandSize.width - 14.0 - UIScreenPixel, y: textFrame.maxY - expandSize.height), size: expandSize)
self.expandNode.frame = expandFrame
self.expandButonNode.frame = expandFrame.insetBy(dx: -8.0, dy: -8.0)

View File

@ -0,0 +1,243 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import ConfettiEffect
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import TelegramStringFormatting
final class PeerInfoBirthdayOverlay: ASDisplayNode {
private let context: AccountContext
private var disposable: Disposable?
init(context: AccountContext) {
self.context = context
super.init()
self.isUserInteractionEnabled = false
}
deinit {
self.disposable?.dispose()
}
func setup(size: CGSize, birthday: TelegramBirthday, sourceRect: CGRect?) {
self.setupAnimations(size: size, birthday: birthday, sourceRect: sourceRect)
Queue.mainQueue().after(0.1) {
self.view.addSubview(ConfettiView(frame: CGRect(origin: .zero, size: size)))
}
}
private func setupAnimations(size: CGSize, birthday: TelegramBirthday, sourceRect: CGRect?) {
self.disposable = (combineLatest(
self.context.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false),
self.context.engine.stickers.loadedStickerPack(reference: .name("FestiveFontEmoji"), forceActualized: false)
)
|> map { animatedEmoji, numbers -> (FileMediaReference?, [FileMediaReference]) in
var effectFile: FileMediaReference?
if case let .result(_, items, _) = animatedEmoji {
let randomKey = ["🎉", "🎈", "🎆"].randomElement()!
outer: for item in items {
let indexKeys = item.getStringRepresentationsOfIndexKeys()
for key in indexKeys {
if key == randomKey {
effectFile = .stickerPack(stickerPack: .animatedEmojiAnimations, media: item.file)
break outer
}
}
}
}
var numberFiles: [FileMediaReference] = []
if let age = ageForBirthday(birthday), case let .result(info, items, _) = numbers {
let ageKeys = ageToKeys(age)
for ageKey in ageKeys {
for item in items {
let indexKeys = item.getStringRepresentationsOfIndexKeys()
for key in indexKeys {
if key == ageKey {
numberFiles.append(.stickerPack(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), media: item.file))
}
}
}
}
}
return (effectFile, numberFiles)
}
|> deliverOnMainQueue).start(next: { [weak self] effectAndNumberFiles in
guard let self else {
return
}
let (effectFile, numberFiles) = effectAndNumberFiles
if let effectFile {
let _ = freeMediaFileInteractiveFetched(account: self.context.account, userLocation: .peer(self.context.account.peerId), fileReference: effectFile).startStandalone()
self.setupEffectAnimation(size: size, file: effectFile)
}
for file in numberFiles {
let _ = freeMediaFileInteractiveFetched(account: self.context.account, userLocation: .peer(self.context.account.peerId), fileReference: file).startStandalone()
}
self.setupNumberAnimations(size: size, files: numberFiles, sourceRect: sourceRect)
})
}
private func setupEffectAnimation(size: CGSize, file: FileMediaReference) {
guard let dimensions = file.media.dimensions else {
return
}
let minSide = min(size.width, size.height)
let pixelSize = dimensions.cgSize.aspectFitted(CGSize(width: 512.0, height: 512.0))
let animationSize = dimensions.cgSize.aspectFitted(CGSize(width: minSide, height: minSide))
let animationNode = DefaultAnimatedStickerNodeImpl()
let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.media.resource, fitzModifier: nil)
let pathPrefix = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.media.resource.id)
animationNode.setup(source: source, width: Int(pixelSize.width), height: Int(pixelSize.height), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
self.addSubnode(animationNode)
animationNode.updateLayout(size: animationSize)
animationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.height - animationSize.height) / 2.0)), size: animationSize)
animationNode.visibility = true
}
private func setupNumberAnimations(size: CGSize, files: [FileMediaReference], sourceRect: CGRect?) {
guard !files.isEmpty else {
return
}
let startY = sourceRect?.midY ?? 475.0
var offset: CGFloat = 0.0
var scaleDelay: Double = 0.0
if files.count > 1 {
offset -= 45.0
}
for file in files {
guard let dimensions = file.media.dimensions else {
continue
}
let animationSize = dimensions.cgSize.aspectFitted(CGSize(width: 144.0, height: 144.0))
let pixelSize = dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0))
let animationNode = DefaultAnimatedStickerNodeImpl()
let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.media.resource, fitzModifier: nil)
let pathPrefix: String? = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.media.resource.id)
animationNode.setup(source: source, width: Int(pixelSize.width), height: Int(pixelSize.height), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
self.addSubnode(animationNode)
animationNode.updateLayout(size: animationSize)
animationNode.bounds = CGRect(origin: .zero, size: animationSize)
animationNode.transform = CATransform3DMakeScale(0.001, 0.001, 1.0)
animationNode.visibility = true
let path = UIBezierPath()
let startPoint = CGPoint(x: 90.0 + offset * 0.7, y: startY + CGFloat.random(in: -20.0 ..< 20.0))
animationNode.position = startPoint
path.move(to: startPoint)
path.addCurve(to: CGPoint(x: 205.0 + offset, y: -90.0), controlPoint1: CGPoint(x: 213.0 + offset * 0.8, y: 380.0 + CGFloat.random(in: -20.0 ..< 20.0)), controlPoint2: CGPoint(x: 206.0 + offset * 0.8, y: 134.0 + CGFloat.random(in: -20.0 ..< 20.0)))
let riseAnimation = CAKeyframeAnimation(keyPath: "position")
riseAnimation.path = path.cgPath
riseAnimation.duration = 2.2
riseAnimation.calculationMode = .cubic
riseAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn)
riseAnimation.beginTime = CACurrentMediaTime() + 0.5
riseAnimation.isRemovedOnCompletion = false
riseAnimation.fillMode = .forwards
riseAnimation.completion = { [weak self] _ in
self?.removeFromSupernode()
}
animationNode.layer.add(riseAnimation, forKey: "position")
offset += 132.0
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
scaleAnimation.duration = 2.0
scaleAnimation.values = [0.0, 0.45, 1.0]
scaleAnimation.keyTimes = [0.0, 0.3, 1.0]
scaleAnimation.beginTime = CACurrentMediaTime() + scaleDelay
scaleAnimation.isRemovedOnCompletion = false
scaleAnimation.fillMode = .forwards
animationNode.layer.add(scaleAnimation, forKey: "scale")
scaleDelay += 0.3
}
}
static func preloadBirthdayAnimations(context: AccountContext, birthday: TelegramBirthday) {
let preload = combineLatest(
context.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false),
context.engine.stickers.loadedStickerPack(reference: .name("FestiveFontEmoji"), forceActualized: false)
)
|> mapToSignal { animatedEmoji, numbers -> Signal<Never, NoError> in
var signals: [Signal<FetchResourceSourceType, FetchResourceError>] = []
if case let .result(_, items, _) = animatedEmoji {
for item in items {
let indexKeys = item.getStringRepresentationsOfIndexKeys()
for key in indexKeys {
if ["🎉", "🎈", "🎆"].contains(key) {
signals.append(freeMediaFileInteractiveFetched(account: context.account, userLocation: .peer(context.account.peerId), fileReference: .stickerPack(stickerPack: .animatedEmojiAnimations, media: item.file)))
}
}
}
}
if let age = ageForBirthday(birthday), case let .result(info, items, _) = numbers {
let ageKeys = ageToKeys(age)
for item in items {
let indexKeys = item.getStringRepresentationsOfIndexKeys()
for key in indexKeys {
if ageKeys.contains(key) {
signals.append(freeMediaFileInteractiveFetched(account: context.account, userLocation: .peer(context.account.peerId), fileReference: .stickerPack(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), media: item.file)))
}
}
}
}
if !signals.isEmpty {
return combineLatest(signals)
|> `catch` { _ in
return .complete()
}
|> ignoreValues
} else {
return .complete()
}
}
let _ = preload.startStandalone()
}
}
private func ageToKeys(_ age: Int) -> [String] {
return "\(age)".map { String($0) }.map {
switch $0 {
case "0":
return "0"
case "1":
return "1"
case "2":
return "2"
case "3":
return "3"
case "4":
return "4"
case "5":
return "5"
case "6":
return "6"
case "7":
return "7"
case "8":
return "8"
case "9":
return "9"
default:
return $0
}
}
}

View File

@ -140,7 +140,7 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
private let itemContainerNode: ASDisplayNode
private var currentItems: [PeerInfoScreenItem] = []
private var itemNodes: [AnyHashable: PeerInfoScreenItemNode] = [:]
fileprivate var itemNodes: [AnyHashable: PeerInfoScreenItemNode] = [:]
override init() {
self.backgroundNode = ASDisplayNode()
@ -587,6 +587,7 @@ private final class PeerInfoInteraction {
let updateBirthdate: (TelegramBirthday??) -> Void
let updateIsEditingBirthdate: (Bool) -> Void
let openBirthdatePrivacy: () -> Void
let openPremiumGift: () -> Void
let editingOpenPersonalChannel: () -> Void
init(
@ -646,6 +647,7 @@ private final class PeerInfoInteraction {
updateBirthdate: @escaping (TelegramBirthday??) -> Void,
updateIsEditingBirthdate: @escaping (Bool) -> Void,
openBirthdatePrivacy: @escaping () -> Void,
openPremiumGift: @escaping () -> Void,
editingOpenPersonalChannel: @escaping () -> Void
) {
self.openUsername = openUsername
@ -704,6 +706,7 @@ private final class PeerInfoInteraction {
self.updateBirthdate = updateBirthdate
self.updateIsEditingBirthdate = updateIsEditingBirthdate
self.openBirthdatePrivacy = openBirthdatePrivacy
self.openPremiumGift = openPremiumGift
self.editingOpenPersonalChannel = editingOpenPersonalChannel
}
}
@ -1133,21 +1136,21 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat
return result
}
private enum InfoSection: Int, CaseIterable {
case groupLocation
case calls
case personalChannel
case peerInfo
case peerMembers
}
private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] {
guard let data = data else {
return []
}
enum Section: Int, CaseIterable {
case groupLocation
case calls
case personalChannel
case peerInfo
case peerMembers
}
var items: [Section: [PeerInfoScreenItem]] = [:]
for section in Section.allCases {
var items: [InfoSection: [PeerInfoScreenItem]] = [:]
for section in InfoSection.allCases {
items[section] = []
}
@ -1227,7 +1230,14 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
if let cachedData = data.cachedData as? CachedUserData {
if let birthday = cachedData.birthday {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 400, label: "date of birth", text: stringForFullBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, action: nil, longTapAction: nil, contextAction: nil, requestLayout: {
var hasBirthdayToday = false
let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date())
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
hasBirthdayToday = true
}
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 400, context: context, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, leftIcon: hasBirthdayToday ? .birthday : nil, icon: hasBirthdayToday ? .premiumGift : nil, action: nil, longTapAction: nil, iconAction: {
interaction.openPremiumGift()
}, contextAction: nil, requestLayout: {
}))
}
@ -1592,7 +1602,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}
var result: [(AnyHashable, [PeerInfoScreenItem])] = []
for section in Section.allCases {
for section in InfoSection.allCases {
if let sectionItems = items[section], !sectionItems.isEmpty {
result.append((section, sectionItems))
}
@ -2673,6 +2683,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.openBirthdatePrivacy()
}
},
openPremiumGift: { [weak self] in
if let self {
self.openPremiumGift()
}
},
editingOpenPersonalChannel: { [weak self] in
guard let self else {
return
@ -4466,8 +4481,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
membersUpdated = true
}
var infoUpdated = false // previousData != nil && (previousData?.cachedData == nil) != (data.cachedData == nil)
var infoUpdated = false
var previousCall: CachedChannelData.ActiveCall?
var currentCall: CachedChannelData.ActiveCall?
@ -4539,6 +4554,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
infoUpdated = true
}
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: self.didSetReady && (membersUpdated || infoUpdated) ? .animated(duration: 0.3, curve: .spring) : .immediate)
if let cachedData = data.cachedData as? CachedUserData, let _ = cachedData.birthday {
self.maybePlayBirthdayAnimation()
}
}
}
@ -5695,34 +5714,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
if let strongSelf = self {
var pushControllerImpl: ((ViewController) -> Void)?
let controller = PremiumGiftScreen(context: strongSelf.context, peerIds: [strongSelf.peerId], options: cachedData.premiumGiftOptions, source: .profile, pushController: { c in
pushControllerImpl?(c)
}, completion: { [weak self] in
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is PeerInfoScreen) && !($0 is PremiumGiftScreen) }
var foundController = false
for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: strongSelf.peerId) = chatController.chatLocation {
chatController.hintPlayNextOutgoingGift()
foundController = true
break
}
}
if !foundController {
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: strongSelf.peerId), subject: nil, botStart: nil, mode: .standard(.default))
chatController.hintPlayNextOutgoingGift()
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
}
})
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
strongSelf.controller?.push(controller)
if let self {
self.openPremiumGift()
}
})))
}
@ -8898,6 +8891,39 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
controller.present(self.context.sharedContext.makeChatQrCodeScreen(context: self.context, peer: peer, threadId: threadId, temporary: temporary), in: .window(.root))
}
private func openPremiumGift() {
guard let cachedData = self.data?.cachedData as? CachedUserData else {
return
}
var pushControllerImpl: ((ViewController) -> Void)?
let controller = PremiumGiftScreen(context: self.context, peerIds: [self.peerId], options: cachedData.premiumGiftOptions, source: .profile, pushController: { c in
pushControllerImpl?(c)
}, completion: { [weak self] in
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is PeerInfoScreen) && !($0 is PremiumGiftScreen) }
var foundController = false
for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: strongSelf.peerId) = chatController.chatLocation {
chatController.hintPlayNextOutgoingGift()
foundController = true
break
}
}
if !foundController {
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: strongSelf.peerId), subject: nil, botStart: nil, mode: .standard(.default))
chatController.hintPlayNextOutgoingGift()
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
}
})
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
self.controller?.push(controller)
}
private func openPostStory() {
self.postingAvailabilityDisposable?.dispose()
@ -10927,6 +10953,38 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.controller?.present(textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}))
}
private var didPlayBirthdayAnimation = false
private weak var birthdayOverlayNode: PeerInfoBirthdayOverlay?
func maybePlayBirthdayAnimation() {
guard !self.didPlayBirthdayAnimation, !self.isSettings && !self.isMediaOnly, let cachedData = self.data?.cachedData as? CachedUserData, let birthday = cachedData.birthday, let (layout, _) = self.validLayout else {
return
}
self.didPlayBirthdayAnimation = true
var hasBirthdayToday = false
let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date())
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
hasBirthdayToday = true
}
if hasBirthdayToday {
Queue.mainQueue().after(0.3) {
var birthdayItemFrame: CGRect?
if let section = self.regularSections[InfoSection.peerInfo] {
if let birthdayItem = section.itemNodes[AnyHashable(400)] {
birthdayItemFrame = birthdayItem.view.convert(birthdayItem.view.bounds, to: self.view)
}
}
let overlayNode = PeerInfoBirthdayOverlay(context: self.context)
overlayNode.frame = CGRect(origin: .zero, size: layout.size)
overlayNode.setup(size: layout.size, birthday: birthday, sourceRect: birthdayItemFrame)
self.addSubnode(overlayNode)
}
}
}
}
public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortcutResponder {
@ -11721,6 +11779,10 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
sourceController.presentInGlobalOverlay(contextController)
})
}
public static func preloadBirthdayAnimations(context: AccountContext, birthday: TelegramBirthday) {
PeerInfoBirthdayOverlay.preloadBirthdayAnimations(context: context, birthday: birthday)
}
}
private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource {

View File

@ -0,0 +1,19 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ScrollComponent",
module_name = "ScrollComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/ComponentFlow",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,150 @@
import Foundation
import UIKit
import ComponentFlow
import Display
public final class ScrollChildEnvironment: Equatable {
public let insets: UIEdgeInsets
public init(insets: UIEdgeInsets) {
self.insets = insets
}
public static func ==(lhs: ScrollChildEnvironment, rhs: ScrollChildEnvironment) -> Bool {
if lhs.insets != rhs.insets {
return false
}
return true
}
}
public final class ScrollComponent<ChildEnvironment: Equatable>: Component {
public typealias EnvironmentType = ChildEnvironment
public class ExternalState {
public var contentHeight: CGFloat = 0.0
public init() {
}
}
let content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>
let externalState: ExternalState?
let contentInsets: UIEdgeInsets
let contentOffsetUpdated: (_ top: CGFloat, _ bottom: CGFloat) -> Void
let contentOffsetWillCommit: (UnsafeMutablePointer<CGPoint>) -> Void
let resetScroll: ActionSlot<Void>
public init(
content: AnyComponent<(ChildEnvironment, ScrollChildEnvironment)>,
externalState: ExternalState? = nil,
contentInsets: UIEdgeInsets,
contentOffsetUpdated: @escaping (_ top: CGFloat, _ bottom: CGFloat) -> Void,
contentOffsetWillCommit: @escaping (UnsafeMutablePointer<CGPoint>) -> Void,
resetScroll: ActionSlot<Void> = ActionSlot()
) {
self.content = content
self.externalState = externalState
self.contentInsets = contentInsets
self.contentOffsetUpdated = contentOffsetUpdated
self.contentOffsetWillCommit = contentOffsetWillCommit
self.resetScroll = resetScroll
}
public static func ==(lhs: ScrollComponent, rhs: ScrollComponent) -> Bool {
if lhs.content != rhs.content {
return false
}
if lhs.contentInsets != rhs.contentInsets {
return false
}
return true
}
public final class View: UIScrollView, UIScrollViewDelegate {
private var component: ScrollComponent<ChildEnvironment>?
private let contentView: ComponentHostView<(ChildEnvironment, ScrollChildEnvironment)>
override init(frame: CGRect) {
self.contentView = ComponentHostView()
super.init(frame: frame)
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.contentInsetAdjustmentBehavior = .never
}
self.delegate = self
self.showsVerticalScrollIndicator = false
self.showsHorizontalScrollIndicator = false
self.canCancelContentTouches = true
self.addSubview(self.contentView)
}
public override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
private var ignoreDidScroll = false
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let component = self.component, !self.ignoreDidScroll else {
return
}
let topOffset = scrollView.contentOffset.y
let bottomOffset = max(0.0, scrollView.contentSize.height - scrollView.contentOffset.y - scrollView.frame.height)
component.contentOffsetUpdated(topOffset, bottomOffset)
}
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard let component = self.component, !self.ignoreDidScroll else {
return
}
component.contentOffsetWillCommit(targetContentOffset)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ScrollComponent<ChildEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ChildEnvironment>, transition: Transition) -> CGSize {
let contentSize = self.contentView.update(
transition: transition,
component: component.content,
environment: {
environment[ChildEnvironment.self]
ScrollChildEnvironment(insets: component.contentInsets)
},
containerSize: CGSize(width: availableSize.width, height: .greatestFiniteMagnitude)
)
transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: contentSize), completion: nil)
component.resetScroll.connect { [weak self] _ in
self?.setContentOffset(.zero, animated: false)
}
if self.contentSize != contentSize {
self.ignoreDidScroll = true
self.contentSize = contentSize
self.ignoreDidScroll = false
}
if self.scrollIndicatorInsets != component.contentInsets {
self.scrollIndicatorInsets = component.contentInsets
}
component.externalState?.contentHeight = contentSize.height
self.component = component
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ChildEnvironment>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -1612,7 +1612,7 @@ final class ChannelAppearanceScreenComponent: Component {
sectionId: 0,
themes: chatThemes,
hasNoTheme: true,
animatedEmojiStickers: component.context.animatedEmojiStickers,
animatedEmojiStickers: component.context.animatedEmojiStickersValue,
themeSpecificAccentColors: [:],
themeSpecificChatWallpapers: [:],
nightMode: environment.theme.overallDarkAppearance,

View File

@ -418,8 +418,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
if let selectedWallpaper, !selectedWallpaper.isEmoticon {
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: selectedWallpaper, channelMode: true, isEditable: false, isSelected: true))
} else {
let emojiFile = context.animatedEmojiStickers[""]?.first?.file
let emojiFile = context.animatedEmojiStickersValue[""]?.first?.file
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: .color(0), isEmpty: true, emoji: emojiFile, channelMode: true, isEditable: false, isSelected: selectedWallpaper == nil))
}
index += 1
@ -441,7 +440,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
isSelected = true
}
let emoji = context.animatedEmojiStickers[themeEmoticon]
let emoji = context.animatedEmojiStickersValue[themeEmoticon]
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: updatedWallpaper, emoji: emoji?.first?.file, channelMode: true, isEditable: false, isSelected: isSelected))
index += 1
}

View File

@ -261,13 +261,13 @@ public final class TextFieldComponent: Component {
let inputState = f(self.inputState)
let currentAttributedText = self.textView.attributedText
let updatedAttributedText = textAttributedStringForStateText(inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.textColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
let updatedAttributedText = textAttributedStringForStateText(inputState.inputText, fontSize: component.fontSize, textColor: component.textColor, accentTextColor: component.textColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
if currentAttributedText != updatedAttributedText {
self.textView.attributedText = updatedAttributedText
}
self.textView.selectedRange = NSMakeRange(inputState.selectionRange.lowerBound, inputState.selectionRange.count)
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
self.updateEntities()
@ -386,7 +386,7 @@ public final class TextFieldComponent: Component {
guard let component = self.component else {
return
}
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize)
self.textView.updateTextContainerInset()
@ -819,7 +819,7 @@ public final class TextFieldComponent: Component {
self.textView.isScrollEnabled = false
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickers.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textView: self.textView, primaryTextColor: component.textColor, accentTextColor: component.textColor, baseFontSize: component.fontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(component.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize)
if self.textView.subviews.count > 1, animated {

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tonads.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,114 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 21.000000 17.509888 cm
0.000000 0.000000 0.000000 scn
25.000000 50.339111 m
38.807117 50.339111 50.000000 40.163765 50.000000 27.611839 c
50.000000 15.059914 38.807117 4.884567 25.000000 4.884567 c
22.883152 4.884567 20.827755 5.123745 18.864723 5.573997 c
18.493393 5.659168 18.071770 5.269714 17.367928 4.619576 c
16.516724 3.833317 15.252751 2.665787 13.165784 1.495388 c
10.499415 0.000057 6.805147 0.129784 6.179927 0.392036 c
5.580503 0.643463 6.041882 1.143257 6.854400 2.023418 c
7.416520 2.632336 8.146702 3.423309 8.809960 4.440048 c
10.431929 6.926434 9.761712 9.886513 9.034047 10.419254 c
3.343933 14.585117 0.000000 20.453083 0.000000 27.611839 c
0.000000 40.163765 11.192881 50.339111 25.000000 50.339111 c
h
21.260956 22.926550 m
18.138887 22.926550 l
15.520651 22.926550 13.398149 25.049055 13.398149 27.667292 c
13.398149 30.285530 15.520653 32.408035 18.138891 32.408035 c
21.260958 32.408035 l
21.654942 32.408035 21.851933 32.408035 22.043053 32.428761 c
22.569538 32.485859 23.076622 32.659843 23.527260 32.938004 c
23.690845 33.038979 23.846344 33.159920 24.157335 33.401802 c
27.425278 35.943535 l
29.389456 37.471230 30.371544 38.235077 31.196095 38.228062 c
31.913391 38.221958 32.589340 37.891361 33.034534 37.328911 c
33.546295 36.682358 33.546295 35.438187 33.546295 32.949852 c
33.546295 22.384739 l
33.546295 19.896397 33.546295 18.652227 33.034534 18.005672 c
32.589340 17.443222 31.913391 17.112625 31.196095 17.106522 c
30.371544 17.099506 29.389454 17.863354 27.425274 19.391050 c
24.157333 21.932781 l
24.157320 21.932793 l
24.157303 21.932804 l
23.846334 22.174671 23.690844 22.295609 23.527260 22.396580 c
23.076622 22.674740 22.569538 22.848724 22.043053 22.905823 c
21.851933 22.926550 21.654942 22.926550 21.260956 22.926550 c
h
18.138889 19.505793 m
18.138889 20.121405 18.138889 20.429213 18.250458 20.667818 c
18.368120 20.919455 18.570433 21.121767 18.822067 21.239428 c
19.060675 21.350996 19.368481 21.350996 19.984095 21.350996 c
21.034426 21.350996 l
21.650040 21.350996 21.957848 21.350996 22.196453 21.239428 c
22.448090 21.121767 22.650402 20.919455 22.768063 20.667818 c
22.879631 20.429213 22.879631 20.121405 22.879631 19.505791 c
22.879631 18.185753 l
22.879631 16.876637 21.818378 15.815388 20.509260 15.815388 c
19.200142 15.815388 18.138889 16.876637 18.138889 18.185757 c
18.138889 19.505793 l
h
f*
n
Q
endstream
endobj
3 0 obj
2430
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 90.000000 90.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002520 00000 n
0000002543 00000 n
0000002716 00000 n
0000002790 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2849
%%EOF

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "lock_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,139 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 6.210022 4.335083 cm
0.000000 0.000000 0.000000 scn
4.455003 16.914955 m
4.455003 19.309111 6.395849 21.249956 8.790003 21.249956 c
11.184156 21.249956 13.125002 19.309111 13.125002 16.914955 c
13.125002 13.826342 l
12.827101 13.829962 12.501101 13.829959 12.143833 13.829956 c
12.143822 13.829956 l
12.115000 13.829956 l
5.465001 13.829956 l
5.436179 13.829956 l
5.436167 13.829956 l
5.078901 13.829959 4.752903 13.829961 4.455003 13.826342 c
4.455003 16.914955 l
h
3.125003 13.753979 m
3.125003 16.914955 l
3.125003 20.043650 5.661310 22.579956 8.790003 22.579956 c
11.918695 22.579956 14.455002 20.043648 14.455002 16.914955 c
14.455002 13.753978 l
14.866020 13.700545 15.234455 13.605986 15.578876 13.430495 c
16.268490 13.079119 16.829163 12.518445 17.180538 11.828832 c
17.399757 11.398593 17.492687 10.930883 17.536919 10.389503 c
17.580009 9.862136 17.580006 9.209549 17.580002 8.394023 c
17.580002 8.393763 l
17.580002 8.364956 l
17.580002 5.464956 l
17.580002 5.436150 l
17.580002 5.435892 l
17.580006 4.620365 17.580009 3.967777 17.536919 3.440411 c
17.492687 2.899029 17.399757 2.431320 17.180538 2.001080 c
16.829163 1.311466 16.268490 0.750793 15.578876 0.399418 c
15.148636 0.180199 14.680927 0.087269 14.139546 0.043037 c
13.612123 -0.000053 12.959455 -0.000050 12.143806 -0.000044 c
12.115000 -0.000044 l
5.465001 -0.000044 l
5.436195 -0.000044 l
4.620547 -0.000050 3.967877 -0.000053 3.440454 0.043037 c
2.899074 0.087269 2.431364 0.180199 2.001125 0.399418 c
1.311511 0.750793 0.750837 1.311466 0.399461 2.001080 c
0.180244 2.431320 0.087314 2.899029 0.043081 3.440411 c
-0.000010 3.967829 -0.000006 4.620495 0.000000 5.436134 c
0.000000 5.464956 l
0.000000 8.364956 l
0.000000 8.393778 l
-0.000006 9.209418 -0.000010 9.862083 0.043081 10.389503 c
0.087314 10.930883 0.180244 11.398593 0.399461 11.828832 c
0.750837 12.518445 1.311511 13.079119 2.001125 13.430495 c
2.345547 13.605987 2.713983 13.700546 3.125003 13.753979 c
h
2.604933 12.245457 m
2.816429 12.353219 3.089627 12.423779 3.548759 12.461292 c
4.015654 12.499439 4.613948 12.499956 5.465001 12.499956 c
12.115000 12.499956 l
12.966051 12.499956 13.564346 12.499439 14.031241 12.461292 c
14.490374 12.423779 14.763573 12.353219 14.975067 12.245457 c
15.414425 12.021592 15.771637 11.664382 15.995501 11.225024 c
16.103264 11.013527 16.173824 10.740330 16.211336 10.281199 c
16.249483 9.814302 16.250000 9.216008 16.250000 8.364956 c
16.250000 5.464956 l
16.250000 4.613903 16.249483 4.015610 16.211336 3.548714 c
16.173824 3.089582 16.103264 2.816383 15.995501 2.604889 c
15.771637 2.165531 15.414425 1.808319 14.975067 1.584455 c
14.763573 1.476692 14.490374 1.406132 14.031241 1.368620 c
13.564346 1.330473 12.966052 1.329956 12.115000 1.329956 c
5.465001 1.329956 l
4.613949 1.329956 4.015654 1.330473 3.548759 1.368620 c
3.089627 1.406132 2.816429 1.476692 2.604933 1.584455 c
2.165574 1.808319 1.808364 2.165531 1.584500 2.604889 c
1.476737 2.816383 1.406177 3.089582 1.368664 3.548714 c
1.330518 4.015610 1.330000 4.613903 1.330000 5.464956 c
1.330000 8.364956 l
1.330000 9.216008 1.330518 9.814302 1.368664 10.281199 c
1.406177 10.740330 1.476737 11.013527 1.584500 11.225024 c
1.808364 11.664382 2.165574 12.021592 2.604933 12.245457 c
h
f*
n
Q
endstream
endobj
3 0 obj
3301
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003391 00000 n
0000003414 00000 n
0000003587 00000 n
0000003661 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3720
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "linktochat.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,195 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 5.000000 4.484375 cm
0.000000 0.000000 0.000000 scn
10.000000 20.135742 m
15.522848 20.135742 20.000000 16.065603 20.000000 11.044833 c
20.000000 6.024063 15.522848 1.953924 10.000000 1.953924 c
9.153261 1.953924 8.331102 2.049595 7.545889 2.229696 c
7.397357 2.263765 7.228708 2.107983 6.947171 1.847927 c
6.606689 1.533424 6.101101 1.066412 5.266314 0.598253 c
4.199766 0.000120 2.722059 0.052011 2.471971 0.156912 c
2.232201 0.257484 2.416753 0.457399 2.741760 0.809465 c
2.966608 1.053034 3.258681 1.369423 3.523984 1.776117 c
4.172771 2.770672 3.904685 3.954702 3.613619 4.167799 c
1.337573 5.834144 0.000000 8.181331 0.000000 11.044833 c
0.000000 16.065603 4.477152 20.135742 10.000000 20.135742 c
h
14.116262 15.129036 m
13.242537 16.002762 11.825946 16.002762 10.952220 15.129036 c
9.618887 13.795702 l
9.388043 13.564859 9.013773 13.564859 8.782929 13.795702 c
8.552086 14.026546 8.552086 14.400816 8.782929 14.631660 c
10.116262 15.964993 l
11.451675 17.300406 13.616807 17.300406 14.952219 15.964993 c
16.287632 14.629580 16.287632 12.464449 14.952219 11.129036 c
13.618887 9.795703 l
12.283474 8.460291 10.118341 8.460291 8.782929 9.795703 c
8.606529 9.972102 8.453041 10.163509 8.322783 10.366060 c
8.146202 10.640644 8.225649 11.006386 8.500233 11.182966 c
8.774817 11.359548 9.140558 11.280101 9.317140 11.005517 c
9.402049 10.873483 9.502523 10.748023 9.618887 10.631660 c
10.492613 9.757934 11.909203 9.757934 12.782929 10.631660 c
14.116262 11.964993 l
14.989988 12.838720 14.989988 14.255310 14.116262 15.129036 c
h
5.886345 6.898323 m
6.760071 6.024597 8.176661 6.024597 9.050387 6.898323 c
10.383720 8.231657 l
10.614564 8.462500 10.988834 8.462500 11.219678 8.231657 c
11.450521 8.000813 11.450521 7.626543 11.219679 7.395700 c
9.886345 6.062366 l
8.550932 4.726954 6.385800 4.726954 5.050388 6.062366 c
3.714975 7.397779 3.714975 9.562910 5.050388 10.898323 c
6.383721 12.231657 l
7.719133 13.567070 9.884266 13.567070 11.219678 12.231657 c
11.396078 12.055257 11.549566 11.863850 11.679824 11.661299 c
11.856405 11.386715 11.776958 11.020973 11.502375 10.844393 c
11.227790 10.667811 10.862049 10.747258 10.685468 11.021843 c
10.600558 11.153877 10.500084 11.279336 10.383720 11.395700 c
9.509995 12.269425 8.093405 12.269425 7.219678 11.395700 c
5.886345 10.062366 l
5.012619 9.188640 5.012619 7.772050 5.886345 6.898323 c
h
f*
n
Q
endstream
endobj
2 0 obj
2424
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
endstream
endobj
4 0 obj
944
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000002682 00000 n
0000002705 00000 n
0000003897 00000 n
0000003919 00000 n
0000004217 00000 n
0000004319 00000 n
0000004340 00000 n
0000004513 00000 n
0000004587 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
4647
%%EOF

View File

@ -173,21 +173,21 @@ public final class AccountContextImpl: AccountContext {
public let animationRenderer: MultiAnimationRenderer
private var animatedEmojiStickersDisposable: Disposable?
public private(set) var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
private let animatedEmojiStickersValue = Promise<[String: [StickerPackItem]]>()
public var animatedEmojiStickersSignal: Signal<[String: [StickerPackItem]], NoError> {
return self.animatedEmojiStickersValue.get()
public private(set) var animatedEmojiStickersValue: [String: [StickerPackItem]] = [:]
private let animatedEmojiStickersPromise = Promise<[String: [StickerPackItem]]>()
public var animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> {
return self.animatedEmojiStickersPromise.get()
}
private var additionalAnimatedEmojiStickersValue: Promise<[String: [Int: StickerPackItem]]>?
private var additionalAnimatedEmojiStickersPromise: Promise<[String: [Int: StickerPackItem]]>?
public var additionalAnimatedEmojiStickers: Signal<[String: [Int: StickerPackItem]], NoError> {
let additionalAnimatedEmojiStickersValue: Promise<[String: [Int: StickerPackItem]]>
if let current = self.additionalAnimatedEmojiStickersValue {
additionalAnimatedEmojiStickersValue = current
let additionalAnimatedEmojiStickersPromise: Promise<[String: [Int: StickerPackItem]]>
if let current = self.additionalAnimatedEmojiStickersPromise {
additionalAnimatedEmojiStickersPromise = current
} else {
additionalAnimatedEmojiStickersValue = Promise<[String: [Int: StickerPackItem]]>()
self.additionalAnimatedEmojiStickersValue = additionalAnimatedEmojiStickersValue
additionalAnimatedEmojiStickersValue.set(self.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
additionalAnimatedEmojiStickersPromise = Promise<[String: [Int: StickerPackItem]]>()
self.additionalAnimatedEmojiStickersPromise = additionalAnimatedEmojiStickersPromise
additionalAnimatedEmojiStickersPromise.set(self.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|> map { animatedEmoji -> [String: [Int: StickerPackItem]] in
let sequence = "0⃣1⃣2⃣3⃣4⃣5⃣6⃣7⃣8⃣9".strippedEmoji
var animatedEmojiStickers: [String: [Int: StickerPackItem]] = [:]
@ -225,7 +225,7 @@ public final class AccountContextImpl: AccountContext {
return animatedEmojiStickers
})
}
return additionalAnimatedEmojiStickersValue.get()
return additionalAnimatedEmojiStickersPromise.get()
}
private var availableReactionsValue: Promise<AvailableReactions?>?
@ -391,8 +391,8 @@ public final class AccountContextImpl: AccountContext {
guard let strongSelf = self else {
return
}
strongSelf.animatedEmojiStickers = stickers
strongSelf.animatedEmojiStickersValue.set(.single(stickers))
strongSelf.animatedEmojiStickersValue = stickers
strongSelf.animatedEmojiStickersPromise.set(.single(stickers))
})
self.userLimitsConfigurationDisposable = (self.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: account.peerId))

View File

@ -118,7 +118,6 @@ import WallpaperGalleryScreen
import WallpaperGridScreen
import VideoMessageCameraScreen
import TopMessageReactions
import PeerInfoScreen
import AudioWaveform
import PeerNameColorScreen
import ChatEmptyNode
@ -16165,7 +16164,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var didDisplayBirthdayTooltip = false
func displayBirthdayTooltip() {
guard !self.didDisplayBirthdayTooltip, let rect = self.chatDisplayNode.frameForGiftButton(), self.effectiveNavigationController?.topViewController === self, let peer = self.presentationInterfaceState.renderedPeer?.peer.flatMap({ EnginePeer($0) }) else {
guard !self.didDisplayBirthdayTooltip else {
return
}
if let birthday = (self.peerView?.cachedData as? CachedUserData)?.birthday {
PeerInfoScreenImpl.preloadBirthdayAnimations(context: self.context, birthday: birthday)
}
guard let rect = self.chatDisplayNode.frameForGiftButton(), self.effectiveNavigationController?.topViewController === self, let peer = self.presentationInterfaceState.renderedPeer?.peer.flatMap({ EnginePeer($0) }) else {
return
}

View File

@ -1380,9 +1380,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
|> distinctUntilChanged
let animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> = (context as! AccountContextImpl).animatedEmojiStickersSignal
let additionalAnimatedEmojiStickers = (context as! AccountContextImpl).additionalAnimatedEmojiStickers
let animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> = context.animatedEmojiStickers
let additionalAnimatedEmojiStickers = context.additionalAnimatedEmojiStickers
let previousHistoryAppearsCleared = self.previousHistoryAppearsCleared

View File

@ -730,11 +730,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
}
textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickers.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
textInputNode.attributedText = textAttributedStringForStateText(state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
if let presentationInterfaceState = self.presentationInterfaceState {
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickers.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
}
self.updatingInputState = false
@ -2888,7 +2888,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
func chatInputTextNodeDidUpdateText() {
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickers.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize)
self.updateSpoiler()
@ -3058,9 +3058,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
textInputNode.textView.isScrollEnabled = false
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickers.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
refreshChatTextInputAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickers.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
textInputNode.attributedText = textAttributedStringForStateText(self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
if textInputNode.textView.subviews.count > 1, animated {
let containerView = textInputNode.textView.subviews[1]
@ -4301,7 +4301,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
}
let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickers.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
let cleanReplacementString = textAttributedStringForStateText(NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider)
string.replaceCharacters(in: range, with: cleanReplacementString)
self.textInputNode?.attributedText = string
self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0)

View File

@ -640,6 +640,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
return
}
self.contactsNode.editableTokens = []
self.updateTitle()
self.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring))
}
contactsNode.updatedSelection = { [weak self] peers, value in