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 activeAccountContexts: Signal<(primary: AccountContext?, accounts: [(AccountRecordId, AccountContext, Int32)], currentAuth: UnauthorizedAccount?), NoError> { get }
|
||||||
var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> { get }
|
var activeAccountsWithInfo: Signal<(primary: AccountRecordId?, accounts: [AccountWithInfo]), NoError> { get }
|
||||||
|
|
||||||
var presentGlobalController: (ViewController, Any?) -> Void { get }
|
var presentGlobalController: (ViewController, Any?) -> Void { get }
|
||||||
var presentCrossfadeController: () -> Void { get }
|
var presentCrossfadeController: () -> Void { get }
|
||||||
|
|
||||||
@ -1075,7 +1075,9 @@ public protocol AccountContext: AnyObject {
|
|||||||
var animationCache: AnimationCache { get }
|
var animationCache: AnimationCache { get }
|
||||||
var animationRenderer: MultiAnimationRenderer { 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 isPremium: Bool { get }
|
||||||
var userLimits: EngineConfiguration.UserLimits { get }
|
var userLimits: EngineConfiguration.UserLimits { get }
|
||||||
|
@ -129,7 +129,7 @@ public final class AdInfoScreen: ViewController {
|
|||||||
openUrl?(value)
|
openUrl?(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5)
|
textNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2)
|
||||||
|
|
||||||
if !didAddUrl {
|
if !didAddUrl {
|
||||||
didAddUrl = true
|
didAddUrl = true
|
||||||
|
@ -338,7 +338,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
||||||
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
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)
|
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
|
||||||
self.updatingInputState = false
|
self.updatingInputState = false
|
||||||
self.updateTextNodeText(animated: animated)
|
self.updateTextNodeText(animated: animated)
|
||||||
@ -1020,7 +1020,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
public func chatInputTextNodeDidUpdateText() {
|
public func chatInputTextNodeDidUpdateText() {
|
||||||
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
|
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
|
||||||
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
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)
|
refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize)
|
||||||
|
|
||||||
self.updateSpoiler()
|
self.updateSpoiler()
|
||||||
@ -1192,9 +1192,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
|
|
||||||
textInputNode.textView.isScrollEnabled = false
|
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 {
|
if textInputNode.textView.subviews.count > 1, animated {
|
||||||
let containerView = textInputNode.textView.subviews[1]
|
let containerView = textInputNode.textView.subviews[1]
|
||||||
@ -1349,7 +1349,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
let textFont = Font.regular(baseFontSize)
|
let textFont = Font.regular(baseFontSize)
|
||||||
let accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
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")
|
let range = (attributedText.string as NSString).range(of: "\n")
|
||||||
if range.location != NSNotFound {
|
if range.location != NSNotFound {
|
||||||
@ -1754,7 +1754,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
||||||
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
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)
|
string.replaceCharacters(in: range, with: cleanReplacementString)
|
||||||
self.textInputNode?.attributedText = string
|
self.textInputNode?.attributedText = string
|
||||||
self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0)
|
self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0)
|
||||||
|
@ -157,7 +157,7 @@ public final class ConfettiView: UIView {
|
|||||||
dtAndDamping.append((0.0, 1.0))
|
dtAndDamping.append((0.0, 1.0))
|
||||||
} else if let slowdownStart = self.slowdownStartTimestamps[i] {
|
} else if let slowdownStart = self.slowdownStartTimestamps[i] {
|
||||||
let slowdownDt: Float
|
let slowdownDt: Float
|
||||||
let slowdownDuration: Float = 0.5
|
let slowdownDuration: Float = 0.7
|
||||||
let damping: Float
|
let damping: Float
|
||||||
if currentTime >= slowdownStart && currentTime <= slowdownStart + slowdownDuration {
|
if currentTime >= slowdownStart && currentTime <= slowdownStart + slowdownDuration {
|
||||||
let slowdownTimestamp: Float = currentTime - slowdownStart
|
let slowdownTimestamp: Float = currentTime - slowdownStart
|
||||||
|
@ -375,6 +375,7 @@ public func generateGradientTintedImage(image: UIImage?, colors: [UIColor]) -> U
|
|||||||
public enum GradientImageDirection {
|
public enum GradientImageDirection {
|
||||||
case vertical
|
case vertical
|
||||||
case horizontal
|
case horizontal
|
||||||
|
case diagonal
|
||||||
}
|
}
|
||||||
|
|
||||||
public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [UIColor], locations: [CGFloat], direction: GradientImageDirection = .vertical) -> UIImage? {
|
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 colorSpace = DeviceGraphicsContextSettings.shared.colorSpace
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: &locations)!
|
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) {
|
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
|
||||||
self.validLayout = (size, leftInset, rightInset)
|
self.validLayout = (size, leftInset, rightInset)
|
||||||
let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height))
|
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 {
|
if let actionButton = self.actionButton, let actionButtonLabel = self.actionButtonLabel {
|
||||||
let buttonSize = actionButtonLabel.updateLayout(CGSize(width: size.width, height: size.height))
|
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/Stories/StoryContainerScreen",
|
||||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||||
|
"//submodules/Components/SheetComponent",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||||
"//submodules/QrCodeUI",
|
"//submodules/QrCodeUI",
|
||||||
|
@ -1338,8 +1338,10 @@ private func monetizationEntries(
|
|||||||
state: ChannelStatsControllerState,
|
state: ChannelStatsControllerState,
|
||||||
stats: ChannelStats,
|
stats: ChannelStats,
|
||||||
data: MonetizationStats,
|
data: MonetizationStats,
|
||||||
animatedEmojis: [String: TelegramMediaFile]
|
animatedEmojis: [String: [StickerPackItem]]
|
||||||
) -> [StatsEntry] {
|
) -> [StatsEntry] {
|
||||||
|
let diamond = animatedEmojis["💎"]?.first?.file
|
||||||
|
|
||||||
var entries: [StatsEntry] = []
|
var entries: [StatsEntry] = []
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
entries.append(.adsHeader(presentationData.theme, "Telegram shares 50% of the revenue from ads displayed in your channel. [Learn More]()"))
|
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(.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(.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(.adsBalanceInfo(presentationData.theme, "We will transfer your balance to the TON wallet address you specify. [Learn More]()"))
|
||||||
|
|
||||||
entries.append(.adsTransactionsTitle(presentationData.theme, "TRANSACTION HISTORY"))
|
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(.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."))
|
entries.append(.adsCpmInfo(presentationData.theme, "Switch off ads or set their minimum CPM."))
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
@ -1393,7 +1395,7 @@ private func channelStatsControllerEntries(
|
|||||||
giveawayAvailable: Bool,
|
giveawayAvailable: Bool,
|
||||||
isGroup: Bool,
|
isGroup: Bool,
|
||||||
boostsOnly: Bool,
|
boostsOnly: Bool,
|
||||||
animatedEmojis: [String: TelegramMediaFile],
|
animatedEmojis: [String: [StickerPackItem]],
|
||||||
monetizationData: MonetizationStats?
|
monetizationData: MonetizationStats?
|
||||||
) -> [StatsEntry] {
|
) -> [StatsEntry] {
|
||||||
switch state.section {
|
switch state.section {
|
||||||
@ -1489,31 +1491,6 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
|
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
|
||||||
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
|
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 dismissAllTooltipsImpl: (() -> Void)?
|
||||||
var presentImpl: ((ViewController) -> Void)?
|
var presentImpl: ((ViewController) -> Void)?
|
||||||
var pushImpl: ((ViewController) -> Void)?
|
var pushImpl: ((ViewController) -> Void)?
|
||||||
@ -1721,7 +1698,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
|||||||
boostsContext.state,
|
boostsContext.state,
|
||||||
giftsContext.state,
|
giftsContext.state,
|
||||||
longLoadingSignal,
|
longLoadingSignal,
|
||||||
animatedEmojiStickers.get()
|
context.animatedEmojiStickers
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, longLoading, animatedEmojiStickers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
|> 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
|
section = .boosts
|
||||||
case 2:
|
case 2:
|
||||||
section = .monetization
|
section = .monetization
|
||||||
let _ = (animatedEmojiStickers.get()
|
controller?.push(MonetizationIntroScreen(context: context, openMore: {}))
|
||||||
|> take(1)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak controller] animatedEmojis in
|
|
||||||
controller?.push(MonetizationIntroScreen(context: context, animatedEmojis: animatedEmojis, openMore: {}))
|
|
||||||
})
|
|
||||||
default:
|
default:
|
||||||
section = .stats
|
section = .stats
|
||||||
}
|
}
|
||||||
|
@ -21,18 +21,15 @@ private final class SheetContent: CombinedComponent {
|
|||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let animatedEmojis: [String: TelegramMediaFile]
|
|
||||||
let openMore: () -> Void
|
let openMore: () -> Void
|
||||||
let dismiss: () -> Void
|
let dismiss: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
animatedEmojis: [String: TelegramMediaFile],
|
|
||||||
openMore: @escaping () -> Void,
|
openMore: @escaping () -> Void,
|
||||||
dismiss: @escaping () -> Void
|
dismiss: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.animatedEmojis = animatedEmojis
|
|
||||||
self.openMore = openMore
|
self.openMore = openMore
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
}
|
}
|
||||||
@ -126,7 +123,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let icon = icon.update(
|
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),
|
availableSize: CGSize(width: 90, height: 90),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
@ -163,7 +160,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
titleColor: textColor,
|
titleColor: textColor,
|
||||||
text: "Telegram can display ads in your channel.",
|
text: "Telegram can display ads in your channel.",
|
||||||
textColor: secondaryTextColor,
|
textColor: secondaryTextColor,
|
||||||
iconName: "Chart/Ads",
|
iconName: "Ads/Ads",
|
||||||
iconColor: linkColor
|
iconColor: linkColor
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
@ -176,7 +173,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
titleColor: textColor,
|
titleColor: textColor,
|
||||||
text: "You receive 50% of the ad revenue in TON.",
|
text: "You receive 50% of the ad revenue in TON.",
|
||||||
textColor: secondaryTextColor,
|
textColor: secondaryTextColor,
|
||||||
iconName: "Chart/Split",
|
iconName: "Ads/Split",
|
||||||
iconColor: linkColor
|
iconColor: linkColor
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
@ -189,7 +186,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
titleColor: textColor,
|
titleColor: textColor,
|
||||||
text: "You can withdraw your TON any time.",
|
text: "You can withdraw your TON any time.",
|
||||||
textColor: secondaryTextColor,
|
textColor: secondaryTextColor,
|
||||||
iconName: "Chart/Withdrawal",
|
iconName: "Ads/Withdrawal",
|
||||||
iconColor: linkColor
|
iconColor: linkColor
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
@ -209,7 +206,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
let infoTitleString = "What's #TON?"//.replacingOccurrences(of: "#", with: "# ")
|
let infoTitleString = "What's #TON?"//.replacingOccurrences(of: "#", with: "# ")
|
||||||
let infoTitleAttributedString = NSMutableAttributedString(string: infoTitleString, font: titleFont, textColor: textColor)
|
let infoTitleAttributedString = NSMutableAttributedString(string: infoTitleString, font: titleFont, textColor: textColor)
|
||||||
let range = (infoTitleAttributedString.string as NSString).range(of: "#")
|
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)
|
infoTitleAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiFile.fileId.id, file: emojiFile), range: range)
|
||||||
}
|
}
|
||||||
let infoTitle = infoTitle.update(
|
let infoTitle = infoTitle.update(
|
||||||
@ -317,16 +314,13 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let animatedEmojis: [String: TelegramMediaFile]
|
|
||||||
let openMore: () -> Void
|
let openMore: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
animatedEmojis: [String: TelegramMediaFile],
|
|
||||||
openMore: @escaping () -> Void
|
openMore: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.animatedEmojis = animatedEmojis
|
|
||||||
self.openMore = openMore
|
self.openMore = openMore
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,7 +346,6 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||||||
component: SheetComponent<EnvironmentType>(
|
component: SheetComponent<EnvironmentType>(
|
||||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||||
context: context.component.context,
|
context: context.component.context,
|
||||||
animatedEmojis: context.component.animatedEmojis,
|
|
||||||
openMore: context.component.openMore,
|
openMore: context.component.openMore,
|
||||||
dismiss: {
|
dismiss: {
|
||||||
animateOut.invoke(Action { _ in
|
animateOut.invoke(Action { _ in
|
||||||
@ -421,23 +414,19 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||||||
|
|
||||||
final class MonetizationIntroScreen: ViewControllerComponentContainer {
|
final class MonetizationIntroScreen: ViewControllerComponentContainer {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let animatedEmojis: [String: TelegramMediaFile]
|
|
||||||
private var openMore: (() -> Void)?
|
private var openMore: (() -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
animatedEmojis: [String: TelegramMediaFile],
|
|
||||||
openMore: @escaping () -> Void
|
openMore: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.animatedEmojis = animatedEmojis
|
|
||||||
self.openMore = openMore
|
self.openMore = openMore
|
||||||
|
|
||||||
super.init(
|
super.init(
|
||||||
context: context,
|
context: context,
|
||||||
component: SheetContainerComponent(
|
component: SheetContainerComponent(
|
||||||
context: context,
|
context: context,
|
||||||
animatedEmojis: animatedEmojis,
|
|
||||||
openMore: openMore
|
openMore: openMore
|
||||||
),
|
),
|
||||||
navigationBarAppearance: .none,
|
navigationBarAppearance: .none,
|
||||||
|
@ -484,7 +484,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
|
if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
|
||||||
var canEdit = false
|
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
|
canEdit = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -955,7 +955,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
case .none:
|
case .none:
|
||||||
buttonColor = self.presentationData.theme.list.itemAccentColor
|
buttonColor = self.presentationData.theme.list.itemAccentColor
|
||||||
case let .result(info, _, installed):
|
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
|
buttonColor = installed ? self.presentationData.theme.list.itemAccentColor : self.presentationData.theme.list.itemCheckColors.foregroundColor
|
||||||
} else {
|
} else {
|
||||||
buttonColor = installed ? self.presentationData.theme.list.itemDestructiveColor : self.presentationData.theme.list.itemCheckColors.foregroundColor
|
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
|
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
|
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
|
//TODO:localize
|
||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
if packItems.count > 0 {
|
if packItems.count > 0 {
|
||||||
@ -1402,7 +1402,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
self.requestDismiss()
|
self.requestDismiss()
|
||||||
} else if let (info, _, installed) = self.currentStickerPack {
|
} 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)
|
self.updateIsEditing(!self.isEditing)
|
||||||
return
|
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 let currentContents = self.currentContents, currentContents.count == 1, let content = currentContents.first, case let .result(info, _, installed) = content {
|
||||||
if installed {
|
if installed {
|
||||||
let text: String
|
let text: String
|
||||||
if info.flags.contains(.isCreator) {
|
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
|
||||||
if self.isEditing {
|
if self.isEditing {
|
||||||
var updated = false
|
var updated = false
|
||||||
if let current = self.buttonNode.attributedTitle(for: .normal)?.string, !current.isEmpty && current != self.presentationData.strings.Common_Done {
|
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?.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)
|
self.controller?.dismiss(animated: true, completion: nil)
|
||||||
case let .result(info, items, installed):
|
case let .result(info, items, installed):
|
||||||
isEditable = info.flags.contains(.isCreator)
|
isEditable = info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji)
|
||||||
self.onReady()
|
self.onReady()
|
||||||
if !items.isEmpty && self.currentStickerPack == nil {
|
if !items.isEmpty && self.currentStickerPack == nil {
|
||||||
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
||||||
@ -1740,7 +1740,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
self.updateButton(count: count)
|
self.updateButton(count: count)
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.flags.contains(.isCreator) {
|
if info.flags.contains(.isCreator) && !info.flags.contains(.isEmoji) {
|
||||||
entries.append(.add)
|
entries.append(.add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1841,7 +1841,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
self.currentEntries = entries
|
self.currentEntries = entries
|
||||||
|
|
||||||
if let controller = self.controller {
|
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)
|
self.enqueueTransaction(transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1901,7 +1901,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
actionAreaBottomInset = 2.0
|
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
|
buttonHeight = 42.0
|
||||||
actionAreaTopInset = 1.0
|
actionAreaTopInset = 1.0
|
||||||
actionAreaBottomInset = 2.0
|
actionAreaBottomInset = 2.0
|
||||||
|
@ -280,6 +280,7 @@ public enum PresentationResourceKey: Int32 {
|
|||||||
case chatFreeNavigateButtonIcon
|
case chatFreeNavigateButtonIcon
|
||||||
case chatFreeShareButtonIcon
|
case chatFreeShareButtonIcon
|
||||||
case chatFreeCloseButtonIcon
|
case chatFreeCloseButtonIcon
|
||||||
|
case chatFreeMoreButtonIcon
|
||||||
|
|
||||||
case chatKeyboardActionButtonMessageIcon
|
case chatKeyboardActionButtonMessageIcon
|
||||||
case chatKeyboardActionButtonLinkIcon
|
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? {
|
public static func chatKeyboardActionButtonMessageIconImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatKeyboardActionButtonMessageIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatKeyboardActionButtonMessageIcon.rawValue, { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/BotMessage"), color: theme.chat.inputButtonPanel.buttonTextColor)
|
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: " ")
|
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 {
|
public enum RelativeTimestampFormatDay {
|
||||||
case today
|
case today
|
||||||
case yesterday
|
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.shareButtonNode = updatedShareButtonNode
|
||||||
strongSelf.addSubnode(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 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)
|
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? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
|
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) {
|
if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) {
|
||||||
return result
|
return result
|
||||||
|
@ -3908,7 +3908,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
let shareButtonNode = ChatMessageShareButton()
|
let shareButtonNode = ChatMessageShareButton()
|
||||||
strongSelf.shareButtonNode = shareButtonNode
|
strongSelf.shareButtonNode = shareButtonNode
|
||||||
strongSelf.insertSubnode(shareButtonNode, belowSubnode: strongSelf.messageAccessibilityArea)
|
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 {
|
} else if let shareButtonNode = strongSelf.shareButtonNode {
|
||||||
strongSelf.shareButtonNode = nil
|
strongSelf.shareButtonNode = nil
|
||||||
@ -4749,7 +4754,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
|
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 {
|
if let selectionNode = self.selectionNode {
|
||||||
|
@ -690,7 +690,9 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
|||||||
}
|
}
|
||||||
strongSelf.shareButtonNode = updatedShareButtonNode
|
strongSelf.shareButtonNode = updatedShareButtonNode
|
||||||
strongSelf.addSubnode(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 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)
|
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? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
|
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) {
|
if !self.bounds.contains(point) {
|
||||||
return nil
|
return nil
|
||||||
|
@ -10,12 +10,18 @@ import Postbox
|
|||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
import ChatMessageItemCommon
|
import ChatMessageItemCommon
|
||||||
|
|
||||||
public class ChatMessageShareButton: HighlightableButtonNode {
|
public class ChatMessageShareButton: ASDisplayNode {
|
||||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||||
private var backgroundBlurView: PortalView?
|
private var backgroundBlurView: PortalView?
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
private let topButton: HighlightTrackingButtonNode
|
||||||
private var iconOffset = CGPoint()
|
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 theme: PresentationTheme?
|
||||||
private var isReplies: Bool = false
|
private var isReplies: Bool = false
|
||||||
@ -24,19 +30,51 @@ public class ChatMessageShareButton: HighlightableButtonNode {
|
|||||||
|
|
||||||
private var absolutePosition: (CGRect, CGSize)?
|
private var absolutePosition: (CGRect, CGSize)?
|
||||||
|
|
||||||
public init() {
|
public var pressed: (() -> Void)?
|
||||||
self.iconNode = ASImageNode()
|
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.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) {
|
required public init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
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 {
|
public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize {
|
||||||
var isReplies = false
|
var isReplies = false
|
||||||
@ -64,10 +102,13 @@ public class ChatMessageShareButton: HighlightableButtonNode {
|
|||||||
self.isReplies = isReplies
|
self.isReplies = isReplies
|
||||||
|
|
||||||
var updatedIconImage: UIImage?
|
var updatedIconImage: UIImage?
|
||||||
|
var updatedBottomIconImage: UIImage?
|
||||||
var updatedIconOffset = CGPoint()
|
var updatedIconOffset = CGPoint()
|
||||||
if message.adAttribute != nil {
|
if message.adAttribute != nil {
|
||||||
updatedIconImage = PresentationResourcesChat.chatFreeCloseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
updatedIconImage = PresentationResourcesChat.chatFreeCloseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
||||||
updatedIconOffset = CGPoint(x: UIScreenPixel, y: UIScreenPixel)
|
updatedIconOffset = CGPoint(x: UIScreenPixel, y: UIScreenPixel)
|
||||||
|
|
||||||
|
updatedBottomIconImage = PresentationResourcesChat.chatFreeMoreButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
||||||
} else if case .pinnedMessages = subject {
|
} else if case .pinnedMessages = subject {
|
||||||
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
||||||
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)
|
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)
|
||||||
@ -79,11 +120,62 @@ public class ChatMessageShareButton: HighlightableButtonNode {
|
|||||||
} else {
|
} else {
|
||||||
updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
|
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.topIconNode.image = updatedIconImage
|
||||||
self.iconOffset = updatedIconOffset
|
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)
|
var size = CGSize(width: 30.0, height: 30.0)
|
||||||
|
if message.adAttribute != nil {
|
||||||
|
size.height += 30.0
|
||||||
|
}
|
||||||
var offsetIcon = false
|
var offsetIcon = false
|
||||||
if isReplies, replyCount > 0 {
|
if isReplies, replyCount > 0 {
|
||||||
offsetIcon = true
|
offsetIcon = true
|
||||||
@ -129,13 +221,18 @@ public class ChatMessageShareButton: HighlightableButtonNode {
|
|||||||
backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size)
|
backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0
|
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)
|
if let bottomIconNode = self.bottomIconNode, let bottomButton = self.bottomButton, let bottomImage = bottomIconNode.image {
|
||||||
//self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0, transition: .immediate)
|
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)
|
||||||
if let image = self.iconNode.image {
|
bottomButton.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
|
||||||
if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||||
|
@ -986,7 +986,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
strongSelf.shareButtonNode = updatedShareButtonNode
|
strongSelf.shareButtonNode = updatedShareButtonNode
|
||||||
strongSelf.addSubnode(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 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)
|
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? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
|
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) {
|
if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) {
|
||||||
return result
|
return result
|
||||||
|
@ -2,6 +2,7 @@ import AsyncDisplayKit
|
|||||||
import Display
|
import Display
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import TelegramCore
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import UIKit
|
import UIKit
|
||||||
import AppBundle
|
import AppBundle
|
||||||
@ -9,6 +10,7 @@ import TelegramStringFormatting
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TextLoadingEffect
|
import TextLoadingEffect
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
|
||||||
enum PeerInfoScreenLabeledValueTextColor {
|
enum PeerInfoScreenLabeledValueTextColor {
|
||||||
case primary
|
case primary
|
||||||
@ -20,6 +22,10 @@ enum PeerInfoScreenLabeledValueTextBehavior: Equatable {
|
|||||||
case multiLine(maxLines: Int, enabledEntities: EnabledEntityTypes)
|
case multiLine(maxLines: Int, enabledEntities: EnabledEntityTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PeerInfoScreenLabeledValueLeftIcon {
|
||||||
|
case birthday
|
||||||
|
}
|
||||||
|
|
||||||
enum PeerInfoScreenLabeledValueIcon {
|
enum PeerInfoScreenLabeledValueIcon {
|
||||||
case qrCode
|
case qrCode
|
||||||
case premiumGift
|
case premiumGift
|
||||||
@ -44,11 +50,13 @@ private struct TextLinkItemSource: Equatable {
|
|||||||
|
|
||||||
final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
|
final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
|
||||||
let id: AnyHashable
|
let id: AnyHashable
|
||||||
|
let context: AccountContext?
|
||||||
let label: String
|
let label: String
|
||||||
let text: String
|
let text: String
|
||||||
let additionalText: String?
|
let additionalText: String?
|
||||||
let textColor: PeerInfoScreenLabeledValueTextColor
|
let textColor: PeerInfoScreenLabeledValueTextColor
|
||||||
let textBehavior: PeerInfoScreenLabeledValueTextBehavior
|
let textBehavior: PeerInfoScreenLabeledValueTextBehavior
|
||||||
|
let leftIcon: PeerInfoScreenLabeledValueLeftIcon?
|
||||||
let icon: PeerInfoScreenLabeledValueIcon?
|
let icon: PeerInfoScreenLabeledValueIcon?
|
||||||
let action: ((ASDisplayNode, Promise<Bool>?) -> Void)?
|
let action: ((ASDisplayNode, Promise<Bool>?) -> Void)?
|
||||||
let longTapAction: ((ASDisplayNode) -> Void)?
|
let longTapAction: ((ASDisplayNode) -> Void)?
|
||||||
@ -59,11 +67,13 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
|
|||||||
|
|
||||||
init(
|
init(
|
||||||
id: AnyHashable,
|
id: AnyHashable,
|
||||||
|
context: AccountContext? = nil,
|
||||||
label: String,
|
label: String,
|
||||||
text: String,
|
text: String,
|
||||||
additionalText: String? = nil,
|
additionalText: String? = nil,
|
||||||
textColor: PeerInfoScreenLabeledValueTextColor = .primary,
|
textColor: PeerInfoScreenLabeledValueTextColor = .primary,
|
||||||
textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine,
|
textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine,
|
||||||
|
leftIcon: PeerInfoScreenLabeledValueLeftIcon? = nil,
|
||||||
icon: PeerInfoScreenLabeledValueIcon? = nil,
|
icon: PeerInfoScreenLabeledValueIcon? = nil,
|
||||||
action: ((ASDisplayNode, Promise<Bool>?) -> Void)?,
|
action: ((ASDisplayNode, Promise<Bool>?) -> Void)?,
|
||||||
longTapAction: ((ASDisplayNode) -> Void)? = nil,
|
longTapAction: ((ASDisplayNode) -> Void)? = nil,
|
||||||
@ -73,11 +83,13 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
|
|||||||
requestLayout: @escaping () -> Void
|
requestLayout: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.context = context
|
||||||
self.label = label
|
self.label = label
|
||||||
self.text = text
|
self.text = text
|
||||||
self.additionalText = additionalText
|
self.additionalText = additionalText
|
||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
self.textBehavior = textBehavior
|
self.textBehavior = textBehavior
|
||||||
|
self.leftIcon = leftIcon
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.action = action
|
self.action = action
|
||||||
self.longTapAction = longTapAction
|
self.longTapAction = longTapAction
|
||||||
@ -132,6 +144,8 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
|||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let iconButtonNode: HighlightTrackingButtonNode
|
private let iconButtonNode: HighlightTrackingButtonNode
|
||||||
|
|
||||||
|
private var animatedEmojiLayer: InlineStickerItemLayer?
|
||||||
|
|
||||||
private var linkHighlightingNode: LinkHighlightingNode?
|
private var linkHighlightingNode: LinkHighlightingNode?
|
||||||
|
|
||||||
private let activateArea: AccessibilityAreaNode
|
private let activateArea: AccessibilityAreaNode
|
||||||
@ -558,13 +572,47 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
|||||||
topOffset += labelSize.height + 3.0
|
topOffset += labelSize.height + 3.0
|
||||||
height += 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 {
|
if textSize.height > 0.0 {
|
||||||
topOffset += textSize.height + 3.0
|
topOffset += textSize.height + 3.0
|
||||||
height += textSize.height
|
height += textSize.height
|
||||||
}
|
}
|
||||||
let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: additionalTextSize)
|
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)
|
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.expandNode.frame = expandFrame
|
||||||
self.expandButonNode.frame = expandFrame.insetBy(dx: -8.0, dy: -8.0)
|
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 PeerInfoChatListPaneNode
|
||||||
import GroupStickerPackSetupController
|
import GroupStickerPackSetupController
|
||||||
import PeerNameColorItem
|
import PeerNameColorItem
|
||||||
import ConfettiEffect
|
|
||||||
|
|
||||||
public enum PeerInfoAvatarEditingMode {
|
public enum PeerInfoAvatarEditingMode {
|
||||||
case generic
|
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) {
|
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
|
||||||
hasBirthdayToday = true
|
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()
|
interaction.openPremiumGift()
|
||||||
}, contextAction: nil, requestLayout: {
|
}, contextAction: nil, requestLayout: {
|
||||||
}))
|
}))
|
||||||
@ -10900,6 +10899,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var didPlayBirthdayAnimation = false
|
private var didPlayBirthdayAnimation = false
|
||||||
|
private weak var birthdayOverlayNode: PeerInfoBirthdayOverlay?
|
||||||
func maybePlayBirthdayAnimation() {
|
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 {
|
guard !self.didPlayBirthdayAnimation, !self.isSettings && !self.isMediaOnly, let cachedData = self.data?.cachedData as? CachedUserData, let birthday = cachedData.birthday, let (layout, _) = self.validLayout else {
|
||||||
return
|
return
|
||||||
@ -10915,7 +10915,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
|
|
||||||
if hasBirthdayToday {
|
if hasBirthdayToday {
|
||||||
Queue.mainQueue().after(0.3) {
|
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)
|
sourceController.presentInGlobalOverlay(contextController)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func preloadBirthdayAnimations(context: AccountContext, birthday: TelegramBirthday) {
|
||||||
|
PeerInfoBirthdayOverlay.preloadBirthdayAnimations(context: context, birthday: birthday)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class SettingsTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
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,
|
sectionId: 0,
|
||||||
themes: chatThemes,
|
themes: chatThemes,
|
||||||
hasNoTheme: true,
|
hasNoTheme: true,
|
||||||
animatedEmojiStickers: component.context.animatedEmojiStickers,
|
animatedEmojiStickers: component.context.animatedEmojiStickersValue,
|
||||||
themeSpecificAccentColors: [:],
|
themeSpecificAccentColors: [:],
|
||||||
themeSpecificChatWallpapers: [:],
|
themeSpecificChatWallpapers: [:],
|
||||||
nightMode: environment.theme.overallDarkAppearance,
|
nightMode: environment.theme.overallDarkAppearance,
|
||||||
|
@ -418,8 +418,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
if let selectedWallpaper, !selectedWallpaper.isEmoticon {
|
if let selectedWallpaper, !selectedWallpaper.isEmoticon {
|
||||||
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: selectedWallpaper, channelMode: true, isEditable: false, isSelected: true))
|
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: selectedWallpaper, channelMode: true, isEditable: false, isSelected: true))
|
||||||
} else {
|
} 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))
|
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: .color(0), isEmpty: true, emoji: emojiFile, channelMode: true, isEditable: false, isSelected: selectedWallpaper == nil))
|
||||||
}
|
}
|
||||||
index += 1
|
index += 1
|
||||||
@ -441,7 +440,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
|
|||||||
isSelected = true
|
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))
|
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: updatedWallpaper, emoji: emoji?.first?.file, channelMode: true, isEditable: false, isSelected: isSelected))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
@ -261,13 +261,13 @@ public final class TextFieldComponent: Component {
|
|||||||
let inputState = f(self.inputState)
|
let inputState = f(self.inputState)
|
||||||
|
|
||||||
let currentAttributedText = self.textView.attributedText
|
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 {
|
if currentAttributedText != updatedAttributedText {
|
||||||
self.textView.attributedText = updatedAttributedText
|
self.textView.attributedText = updatedAttributedText
|
||||||
}
|
}
|
||||||
self.textView.selectedRange = NSMakeRange(inputState.selectionRange.lowerBound, inputState.selectionRange.count)
|
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()
|
self.updateEntities()
|
||||||
|
|
||||||
@ -386,7 +386,7 @@ public final class TextFieldComponent: Component {
|
|||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
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)
|
refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize)
|
||||||
self.textView.updateTextContainerInset()
|
self.textView.updateTextContainerInset()
|
||||||
|
|
||||||
@ -819,7 +819,7 @@ public final class TextFieldComponent: Component {
|
|||||||
|
|
||||||
self.textView.isScrollEnabled = false
|
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)
|
refreshChatTextInputTypingAttributes(self.textView, textColor: component.textColor, baseFontSize: component.fontSize)
|
||||||
|
|
||||||
if self.textView.subviews.count > 1, animated {
|
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
|
public let animationRenderer: MultiAnimationRenderer
|
||||||
|
|
||||||
private var animatedEmojiStickersDisposable: Disposable?
|
private var animatedEmojiStickersDisposable: Disposable?
|
||||||
public private(set) var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
public private(set) var animatedEmojiStickersValue: [String: [StickerPackItem]] = [:]
|
||||||
private let animatedEmojiStickersValue = Promise<[String: [StickerPackItem]]>()
|
private let animatedEmojiStickersPromise = Promise<[String: [StickerPackItem]]>()
|
||||||
public var animatedEmojiStickersSignal: Signal<[String: [StickerPackItem]], NoError> {
|
public var animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> {
|
||||||
return self.animatedEmojiStickersValue.get()
|
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> {
|
public var additionalAnimatedEmojiStickers: Signal<[String: [Int: StickerPackItem]], NoError> {
|
||||||
let additionalAnimatedEmojiStickersValue: Promise<[String: [Int: StickerPackItem]]>
|
let additionalAnimatedEmojiStickersPromise: Promise<[String: [Int: StickerPackItem]]>
|
||||||
if let current = self.additionalAnimatedEmojiStickersValue {
|
if let current = self.additionalAnimatedEmojiStickersPromise {
|
||||||
additionalAnimatedEmojiStickersValue = current
|
additionalAnimatedEmojiStickersPromise = current
|
||||||
} else {
|
} else {
|
||||||
additionalAnimatedEmojiStickersValue = Promise<[String: [Int: StickerPackItem]]>()
|
additionalAnimatedEmojiStickersPromise = Promise<[String: [Int: StickerPackItem]]>()
|
||||||
self.additionalAnimatedEmojiStickersValue = additionalAnimatedEmojiStickersValue
|
self.additionalAnimatedEmojiStickersPromise = additionalAnimatedEmojiStickersPromise
|
||||||
additionalAnimatedEmojiStickersValue.set(self.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|
additionalAnimatedEmojiStickersPromise.set(self.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|
||||||
|> map { animatedEmoji -> [String: [Int: StickerPackItem]] in
|
|> map { animatedEmoji -> [String: [Int: StickerPackItem]] in
|
||||||
let sequence = "0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣".strippedEmoji
|
let sequence = "0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣".strippedEmoji
|
||||||
var animatedEmojiStickers: [String: [Int: StickerPackItem]] = [:]
|
var animatedEmojiStickers: [String: [Int: StickerPackItem]] = [:]
|
||||||
@ -225,7 +225,7 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
return animatedEmojiStickers
|
return animatedEmojiStickers
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return additionalAnimatedEmojiStickersValue.get()
|
return additionalAnimatedEmojiStickersPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var availableReactionsValue: Promise<AvailableReactions?>?
|
private var availableReactionsValue: Promise<AvailableReactions?>?
|
||||||
@ -391,8 +391,8 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.animatedEmojiStickers = stickers
|
strongSelf.animatedEmojiStickersValue = stickers
|
||||||
strongSelf.animatedEmojiStickersValue.set(.single(stickers))
|
strongSelf.animatedEmojiStickersPromise.set(.single(stickers))
|
||||||
})
|
})
|
||||||
|
|
||||||
self.userLimitsConfigurationDisposable = (self.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: account.peerId))
|
self.userLimitsConfigurationDisposable = (self.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: account.peerId))
|
||||||
|
@ -118,7 +118,6 @@ import WallpaperGalleryScreen
|
|||||||
import WallpaperGridScreen
|
import WallpaperGridScreen
|
||||||
import VideoMessageCameraScreen
|
import VideoMessageCameraScreen
|
||||||
import TopMessageReactions
|
import TopMessageReactions
|
||||||
import PeerInfoScreen
|
|
||||||
import AudioWaveform
|
import AudioWaveform
|
||||||
import PeerNameColorScreen
|
import PeerNameColorScreen
|
||||||
import ChatEmptyNode
|
import ChatEmptyNode
|
||||||
@ -16100,7 +16099,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
private var didDisplayBirthdayTooltip = false
|
private var didDisplayBirthdayTooltip = false
|
||||||
func displayBirthdayTooltip() {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1380,9 +1380,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|
||||||
let animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> = (context as! AccountContextImpl).animatedEmojiStickersSignal
|
let animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> = context.animatedEmojiStickers
|
||||||
|
let additionalAnimatedEmojiStickers = context.additionalAnimatedEmojiStickers
|
||||||
let additionalAnimatedEmojiStickers = (context as! AccountContextImpl).additionalAnimatedEmojiStickers
|
|
||||||
|
|
||||||
let previousHistoryAppearsCleared = self.previousHistoryAppearsCleared
|
let previousHistoryAppearsCleared = self.previousHistoryAppearsCleared
|
||||||
|
|
||||||
|
@ -729,11 +729,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
||||||
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
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)
|
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
|
||||||
|
|
||||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
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
|
self.updatingInputState = false
|
||||||
@ -2846,7 +2846,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
func chatInputTextNodeDidUpdateText() {
|
func chatInputTextNodeDidUpdateText() {
|
||||||
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
|
if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState {
|
||||||
let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
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)
|
refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize)
|
||||||
|
|
||||||
self.updateSpoiler()
|
self.updateSpoiler()
|
||||||
@ -3016,9 +3016,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
|
|
||||||
textInputNode.textView.isScrollEnabled = false
|
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 {
|
if textInputNode.textView.subviews.count > 1, animated {
|
||||||
let containerView = textInputNode.textView.subviews[1]
|
let containerView = textInputNode.textView.subviews[1]
|
||||||
@ -4230,7 +4230,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
||||||
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
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)
|
string.replaceCharacters(in: range, with: cleanReplacementString)
|
||||||
self.textInputNode?.attributedText = string
|
self.textInputNode?.attributedText = string
|
||||||
self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0)
|
self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user