mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Birthdays
This commit is contained in:
parent
ff4fcc3ef0
commit
cb7560a34e
@ -903,7 +903,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 }
|
||||
|
||||
@ -1075,7 +1075,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 }
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -280,6 +280,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatFreeNavigateButtonIcon
|
||||
case chatFreeShareButtonIcon
|
||||
case chatFreeCloseButtonIcon
|
||||
case chatFreeMoreButtonIcon
|
||||
|
||||
case chatKeyboardActionButtonMessageIcon
|
||||
case chatKeyboardActionButtonLinkIcon
|
||||
|
@ -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)
|
||||
|
@ -171,6 +171,24 @@ public func stringForCompactBirthday(_ birthday: TelegramBirthday, strings: Pres
|
||||
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
|
||||
|
36
submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD
Normal file
36
submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD
Normal 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",
|
||||
],
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
35
submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD
Normal file
35
submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD
Normal 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",
|
||||
],
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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,6 +22,10 @@ enum PeerInfoScreenLabeledValueTextBehavior: Equatable {
|
||||
case multiLine(maxLines: Int, enabledEntities: EnabledEntityTypes)
|
||||
}
|
||||
|
||||
enum PeerInfoScreenLabeledValueLeftIcon {
|
||||
case birthday
|
||||
}
|
||||
|
||||
enum PeerInfoScreenLabeledValueIcon {
|
||||
case qrCode
|
||||
case premiumGift
|
||||
@ -44,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)?
|
||||
@ -59,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,
|
||||
@ -73,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
|
||||
@ -132,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
|
||||
@ -558,13 +572,47 @@ 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:
|
||||
#if DEBUG
|
||||
file = context.animatedEmojiStickersValue["🔥"]?.first?.file
|
||||
#else
|
||||
file = context.animatedEmojiStickersValue["🎂"]?.first?.file
|
||||
#endif
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -0,0 +1,238 @@
|
||||
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) {
|
||||
self.setupAnimations(size: size, birthday: birthday)
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
self.view.addSubview(ConfettiView(frame: CGRect(origin: .zero, size: size)))
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAnimations(size: CGSize, birthday: TelegramBirthday) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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]) {
|
||||
guard !files.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
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? = nil //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: 475.0 + 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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -106,7 +106,6 @@ import PeerInfoChatPaneNode
|
||||
import PeerInfoChatListPaneNode
|
||||
import GroupStickerPackSetupController
|
||||
import PeerNameColorItem
|
||||
import ConfettiEffect
|
||||
|
||||
public enum PeerInfoAvatarEditingMode {
|
||||
case generic
|
||||
@ -1213,7 +1212,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
|
||||
hasBirthdayToday = true
|
||||
}
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 400, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, icon: hasBirthdayToday ? .premiumGift : nil, action: nil, longTapAction: nil, iconAction: {
|
||||
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: {
|
||||
}))
|
||||
@ -10900,6 +10899,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
|
||||
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
|
||||
@ -10915,7 +10915,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
|
||||
if hasBirthdayToday {
|
||||
Queue.mainQueue().after(0.3) {
|
||||
self.view.addSubview(ConfettiView(frame: CGRect(origin: .zero, size: layout.size)))
|
||||
let overlayNode = PeerInfoBirthdayOverlay(context: self.context)
|
||||
overlayNode.frame = CGRect(origin: .zero, size: layout.size)
|
||||
overlayNode.setup(size: layout.size, birthday: birthday)
|
||||
self.addSubnode(overlayNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11713,6 +11716,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 {
|
||||
|
19
submodules/TelegramUI/Components/ScrollComponent/BUILD
Normal file
19
submodules/TelegramUI/Components/ScrollComponent/BUILD
Normal 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",
|
||||
],
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Ads/AdsLogo.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Ads/AdsLogo.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tonads.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
114
submodules/TelegramUI/Images.xcassets/Ads/AdsLogo.imageset/tonads.pdf
vendored
Normal file
114
submodules/TelegramUI/Images.xcassets/Ads/AdsLogo.imageset/tonads.pdf
vendored
Normal 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
|
9
submodules/TelegramUI/Images.xcassets/Ads/Contents.json
Normal file
9
submodules/TelegramUI/Images.xcassets/Ads/Contents.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
12
submodules/TelegramUI/Images.xcassets/Ads/Privacy.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Ads/Privacy.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lock_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
139
submodules/TelegramUI/Images.xcassets/Ads/Privacy.imageset/lock_30.pdf
vendored
Normal file
139
submodules/TelegramUI/Images.xcassets/Ads/Privacy.imageset/lock_30.pdf
vendored
Normal 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
|
@ -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))
|
||||
|
@ -118,7 +118,6 @@ import WallpaperGalleryScreen
|
||||
import WallpaperGridScreen
|
||||
import VideoMessageCameraScreen
|
||||
import TopMessageReactions
|
||||
import PeerInfoScreen
|
||||
import AudioWaveform
|
||||
import PeerNameColorScreen
|
||||
import ChatEmptyNode
|
||||
@ -16100,7 +16099,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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -729,11 +729,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
|
||||
@ -2846,7 +2846,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()
|
||||
@ -3016,9 +3016,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]
|
||||
@ -4230,7 +4230,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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user