mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
[WIP] Emoji statuses
This commit is contained in:
parent
551674a744
commit
04ea87c1e3
20 changed files with 1377 additions and 116 deletions
|
|
@ -74,6 +74,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import UniversalMediaPlayer
|
|||
import GalleryUI
|
||||
import HierarchyTrackingLayer
|
||||
import TextNodeWithEntities
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
|
||||
public enum ChatListItemContent {
|
||||
public final class DraftState: Equatable {
|
||||
|
|
@ -458,6 +460,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
let pinnedIconNode: ASImageNode
|
||||
var secretIconNode: ASImageNode?
|
||||
var credibilityIconNode: ASImageNode?
|
||||
var credibilityIconView: ComponentHostView<Empty>?
|
||||
let mutedIconNode: ASImageNode
|
||||
|
||||
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
|
||||
|
|
@ -1069,6 +1072,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
var currentPinnedIconImage: UIImage?
|
||||
var currentMutedIconImage: UIImage?
|
||||
var currentCredibilityIconImage: UIImage?
|
||||
var currentCredibilityIconContent: EmojiStatusComponent.Content?
|
||||
var currentSecretIconImage: UIImage?
|
||||
|
||||
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||
|
|
@ -1515,12 +1519,21 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
if let peer = messages.last?.author {
|
||||
if peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isVerified {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||
|
||||
if "".isEmpty {
|
||||
currentCredibilityIconContent = .emojiStatus(status: PeerEmojiStatus(fileId: 5431449001532594346), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
|
||||
} else {
|
||||
currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
|
@ -1529,12 +1542,21 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
|
||||
if peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
currentCredibilityIconContent = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if peer.isVerified {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||
currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||
|
||||
if "".isEmpty {
|
||||
currentCredibilityIconContent = .emojiStatus(status: PeerEmojiStatus(fileId: 5431449001532594346), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
|
||||
} else {
|
||||
currentCredibilityIconContent = .premium(color: item.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2030,6 +2052,35 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
}
|
||||
|
||||
var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.size.width + 3.0 + titleOffset
|
||||
|
||||
if let currentCredibilityIconContent = currentCredibilityIconContent {
|
||||
let credibilityIconView: ComponentHostView<Empty>
|
||||
if let current = strongSelf.credibilityIconView {
|
||||
credibilityIconView = current
|
||||
} else {
|
||||
credibilityIconView = ComponentHostView<Empty>()
|
||||
strongSelf.credibilityIconView = credibilityIconView
|
||||
strongSelf.contextContainer.view.addSubview(credibilityIconView)
|
||||
}
|
||||
let iconSize = credibilityIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: item.context,
|
||||
animationCache: item.interaction.animationCache,
|
||||
animationRenderer: item.interaction.animationRenderer,
|
||||
content: currentCredibilityIconContent,
|
||||
action: nil,
|
||||
longTapAction: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 20.0, height: 20.0)
|
||||
)
|
||||
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0) - UIScreenPixel), size: iconSize))
|
||||
} else if let credibilityIconView = strongSelf.credibilityIconView {
|
||||
strongSelf.credibilityIconView = nil
|
||||
credibilityIconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
||||
let iconNode: ASImageNode
|
||||
if let current = strongSelf.credibilityIconNode {
|
||||
|
|
@ -2041,6 +2092,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
iconNode.displayWithoutProcessing = true
|
||||
strongSelf.contextContainer.addSubnode(iconNode)
|
||||
strongSelf.credibilityIconNode = iconNode
|
||||
iconNode.isHidden = true
|
||||
}
|
||||
iconNode.image = currentCredibilityIconImage
|
||||
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
|
||||
|
|
|
|||
|
|
@ -369,19 +369,23 @@ public struct Transition {
|
|||
}
|
||||
|
||||
public func setAlpha(view: UIView, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
if view.alpha == alpha {
|
||||
self.setAlpha(layer: view.layer, alpha: alpha, completion: completion)
|
||||
}
|
||||
|
||||
public func setAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.opacity == Float(alpha) {
|
||||
completion?(true)
|
||||
return
|
||||
}
|
||||
switch self.animation {
|
||||
case .none:
|
||||
view.alpha = alpha
|
||||
view.layer.removeAnimation(forKey: "opacity")
|
||||
layer.opacity = Float(alpha)
|
||||
layer.removeAnimation(forKey: "opacity")
|
||||
completion?(true)
|
||||
case .curve:
|
||||
let previousAlpha = (view.layer.presentation()?.opacity).flatMap(CGFloat.init) ?? view.alpha
|
||||
view.alpha = alpha
|
||||
self.animateAlpha(view: view, from: previousAlpha, to: alpha, completion: completion)
|
||||
let previousAlpha = layer.presentation()?.opacity ?? layer.opacity
|
||||
layer.opacity = Float(alpha)
|
||||
self.animateAlpha(layer: layer, from: CGFloat(previousAlpha), to: alpha, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -413,6 +417,37 @@ public struct Transition {
|
|||
}
|
||||
}
|
||||
|
||||
public func setTransform(view: UIView, transform: CATransform3D, completion: ((Bool) -> Void)? = nil) {
|
||||
self.setTransform(layer: view.layer, transform: transform, completion: completion)
|
||||
}
|
||||
|
||||
public func setTransform(layer: CALayer, transform: CATransform3D, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
layer.transform = transform
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
let previousValue: CATransform3D
|
||||
if let presentation = layer.presentation() {
|
||||
previousValue = presentation.transform
|
||||
} else {
|
||||
previousValue = layer.transform
|
||||
}
|
||||
layer.transform = transform
|
||||
layer.animate(
|
||||
from: NSValue(caTransform3D: previousValue),
|
||||
to: NSValue(caTransform3D: transform),
|
||||
keyPath: "transform",
|
||||
duration: duration,
|
||||
delay: 0.0,
|
||||
curve: curve,
|
||||
removeOnCompletion: true,
|
||||
additive: false,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func setSublayerTransform(view: UIView, transform: CATransform3D, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
|
|
@ -479,11 +514,15 @@ public struct Transition {
|
|||
}
|
||||
|
||||
public func animateAlpha(view: UIView, from fromValue: CGFloat, to toValue: CGFloat, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animateAlpha(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
public func animateAlpha(layer: CALayer, from fromValue: CGFloat, to toValue: CGFloat, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self.animation {
|
||||
case .none:
|
||||
completion?(true)
|
||||
case let .curve(duration, curve):
|
||||
view.layer.animate(
|
||||
layer.animate(
|
||||
from: fromValue as NSNumber,
|
||||
to: toValue as NSNumber,
|
||||
keyPath: "opacity",
|
||||
|
|
|
|||
|
|
@ -61,3 +61,29 @@ open class SimpleShapeLayer: CAShapeLayer {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
open class SimpleGradientLayer: CAGradientLayer {
|
||||
public var didEnterHierarchy: (() -> Void)?
|
||||
public var didExitHierarchy: (() -> Void)?
|
||||
|
||||
override open func action(forKey event: String) -> CAAction? {
|
||||
if event == kCAOnOrderIn {
|
||||
self.didEnterHierarchy?()
|
||||
} else if event == kCAOnOrderOut {
|
||||
self.didExitHierarchy?()
|
||||
}
|
||||
return nullAction
|
||||
}
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
override public init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,14 @@ public struct CachedPremiumGiftOption: Equatable, PostboxCoding {
|
|||
}
|
||||
}
|
||||
|
||||
public struct PeerEmojiStatus: Equatable, Codable {
|
||||
public var fileId: Int64
|
||||
|
||||
public init(fileId: Int64) {
|
||||
self.fileId = fileId
|
||||
}
|
||||
}
|
||||
|
||||
public final class CachedUserData: CachedPeerData {
|
||||
public let about: String?
|
||||
public let botInfo: BotInfo?
|
||||
|
|
@ -102,6 +110,7 @@ public final class CachedUserData: CachedPeerData {
|
|||
public let photo: TelegramMediaImage?
|
||||
public let premiumGiftOptions: [CachedPremiumGiftOption]
|
||||
public let voiceMessagesAvailable: Bool
|
||||
public let emojiStatus: PeerEmojiStatus?
|
||||
|
||||
public let peerIds: Set<PeerId>
|
||||
public let messageIds: Set<MessageId>
|
||||
|
|
@ -124,11 +133,12 @@ public final class CachedUserData: CachedPeerData {
|
|||
self.photo = nil
|
||||
self.premiumGiftOptions = []
|
||||
self.voiceMessagesAvailable = true
|
||||
self.emojiStatus = nil
|
||||
self.peerIds = Set()
|
||||
self.messageIds = Set()
|
||||
}
|
||||
|
||||
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: TelegramMediaImage?, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool) {
|
||||
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: TelegramMediaImage?, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool, emojiStatus: PeerEmojiStatus?) {
|
||||
self.about = about
|
||||
self.botInfo = botInfo
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
|
|
@ -145,6 +155,7 @@ public final class CachedUserData: CachedPeerData {
|
|||
self.photo = photo
|
||||
self.premiumGiftOptions = premiumGiftOptions
|
||||
self.voiceMessagesAvailable = voiceMessagesAvailable
|
||||
self.emojiStatus = emojiStatus
|
||||
|
||||
self.peerIds = Set<PeerId>()
|
||||
|
||||
|
|
@ -189,6 +200,8 @@ public final class CachedUserData: CachedPeerData {
|
|||
self.premiumGiftOptions = decoder.decodeObjectArrayWithDecoderForKey("pgo") as [CachedPremiumGiftOption]
|
||||
self.voiceMessagesAvailable = decoder.decodeInt32ForKey("vma", orElse: 0) != 0
|
||||
|
||||
self.emojiStatus = decoder.decode(PeerEmojiStatus.self, forKey: "emojiStatus")
|
||||
|
||||
self.peerIds = Set<PeerId>()
|
||||
|
||||
var messageIds = Set<MessageId>()
|
||||
|
|
@ -245,6 +258,12 @@ public final class CachedUserData: CachedPeerData {
|
|||
|
||||
encoder.encodeObjectArray(self.premiumGiftOptions, forKey: "pgo")
|
||||
encoder.encodeInt32(self.voiceMessagesAvailable ? 1 : 0, forKey: "vma")
|
||||
|
||||
if let emojiStatus = self.emojiStatus {
|
||||
encoder.encode(emojiStatus, forKey: "emojiStatus")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "emojiStatus")
|
||||
}
|
||||
}
|
||||
|
||||
public func isEqual(to: CachedPeerData) -> Bool {
|
||||
|
|
@ -258,71 +277,78 @@ public final class CachedUserData: CachedPeerData {
|
|||
if other.canPinMessages != self.canPinMessages {
|
||||
return false
|
||||
}
|
||||
if other.emojiStatus != self.emojiStatus {
|
||||
return false
|
||||
}
|
||||
|
||||
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon && self.photo == other.photo && self.premiumGiftOptions == other.premiumGiftOptions && self.voiceMessagesAvailable == other.voiceMessagesAvailable
|
||||
}
|
||||
|
||||
public func withUpdatedAbout(_ about: String?) -> CachedUserData {
|
||||
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable)
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable, emojiStatus: self.emojiStatus)
|
||||
}
|
||||
|
||||
public func withUpdatedEmojiStatus(_ emojiStatus: PeerEmojiStatus?) -> CachedUserData {
|
||||
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, emojiStatus: emojiStatus)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,5 +53,23 @@ public extension TelegramEngine {
|
|||
public func removeAccountPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
|
||||
return _internal_removeAccountPhoto(network: self.account.network, reference: reference)
|
||||
}
|
||||
|
||||
public func setEmojiStatus(file: TelegramMediaFile?) -> Signal<Never, NoError> {
|
||||
let peerId = self.account.peerId
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
if let file = file {
|
||||
transaction.storeMediaIfNotPresent(media: file)
|
||||
}
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
guard let current = current as? CachedUserData else {
|
||||
return current
|
||||
}
|
||||
return current.withUpdatedEmojiStatus(file.flatMap { file -> PeerEmojiStatus in
|
||||
return PeerEmojiStatus(fileId: file.fileId.id)
|
||||
})
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,6 +290,8 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ChatInputPanelContainer:ChatInputPanelContainer",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent:EmojiSuggestionsComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent:EmojiStatusSelectionComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||
"//submodules/Media/ConvertOpusToAAC:ConvertOpusToAAC",
|
||||
"//submodules/Media/LocalAudioTranscription:LocalAudioTranscription",
|
||||
|
|
|
|||
28
submodules/TelegramUI/Components/EmojiStatusComponent/BUILD
Normal file
28
submodules/TelegramUI/Components/EmojiStatusComponent/BUILD
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "EmojiStatusComponent",
|
||||
module_name = "EmojiStatusComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,341 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import ComponentFlow
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import EmojiTextAttachmentView
|
||||
import AppBundle
|
||||
import TextFormat
|
||||
|
||||
public final class EmojiStatusComponent: Component {
|
||||
public typealias EnvironmentType = Empty
|
||||
|
||||
public enum Content: Equatable {
|
||||
case none
|
||||
case premium(color: UIColor)
|
||||
case verified(fillColor: UIColor, foregroundColor: UIColor)
|
||||
case fake(color: UIColor)
|
||||
case scam(color: UIColor)
|
||||
case emojiStatus(status: PeerEmojiStatus, placeholderColor: UIColor)
|
||||
}
|
||||
|
||||
public let context: AccountContext
|
||||
public let animationCache: AnimationCache
|
||||
public let animationRenderer: MultiAnimationRenderer
|
||||
public let content: Content
|
||||
public let action: (() -> Void)?
|
||||
public let longTapAction: (() -> Void)?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
animationCache: AnimationCache,
|
||||
animationRenderer: MultiAnimationRenderer,
|
||||
content: Content,
|
||||
action: (() -> Void)?,
|
||||
longTapAction: (() -> Void)?
|
||||
) {
|
||||
self.context = context
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.content = content
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiStatusComponent, rhs: EmojiStatusComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.animationCache !== rhs.animationCache {
|
||||
return false
|
||||
}
|
||||
if lhs.animationRenderer !== rhs.animationRenderer {
|
||||
return false
|
||||
}
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private weak var state: EmptyComponentState?
|
||||
private var component: EmojiStatusComponent?
|
||||
private var iconView: UIImageView?
|
||||
private var animationLayer: InlineStickerItemLayer?
|
||||
|
||||
private var emojiFile: TelegramMediaFile?
|
||||
private var emojiFileDisposable: Disposable?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
self.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiFileDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.component?.action?()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func longPressGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .began = recognizer.state {
|
||||
self.component?.longTapAction?()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: EmojiStatusComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.state = state
|
||||
|
||||
var iconImage: UIImage?
|
||||
var emojiFileId: Int64?
|
||||
var emojiPlaceholderColor: UIColor?
|
||||
|
||||
/*
|
||||
if case .fake = credibilityIcon {
|
||||
image = PresentationResourcesChatList.fakeIcon(presentationData.theme, strings: presentationData.strings, type: .regular)
|
||||
} else if case .scam = credibilityIcon {
|
||||
image = PresentationResourcesChatList.scamIcon(presentationData.theme, strings: presentationData.strings, type: .regular)
|
||||
} else if case .verified = credibilityIcon {
|
||||
if let backgroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconForeground") {
|
||||
image = generateImage(backgroundImage.size, contextGenerator: { size, context in
|
||||
if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.saveGState()
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
|
||||
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.restoreGState()
|
||||
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, opaque: false)
|
||||
expandedImage = generateImage(backgroundImage.size, contextGenerator: { size, context in
|
||||
if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.saveGState()
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
|
||||
context.setFillColor(UIColor(rgb: 0xffffff, alpha: 0.75).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.restoreGState()
|
||||
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
|
||||
context.setBlendMode(.clear)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, opaque: false)
|
||||
} else {
|
||||
image = nil
|
||||
}
|
||||
} else if case .premium = credibilityIcon {
|
||||
if let sourceImage = UIImage(bundleImageName: "Peer Info/PremiumIcon") {
|
||||
image = generateImage(sourceImage.size, contextGenerator: { size, context in
|
||||
if let cgImage = sourceImage.cgImage {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, opaque: false)
|
||||
expandedImage = generateImage(sourceImage.size, contextGenerator: { size, context in
|
||||
if let cgImage = sourceImage.cgImage {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||
context.setFillColor(UIColor(rgb: 0xffffff, alpha: 0.75).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, opaque: false)
|
||||
} else {
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if self.component?.content != component.content {
|
||||
switch component.content {
|
||||
case .none:
|
||||
iconImage = nil
|
||||
case let .premium(color):
|
||||
if let sourceImage = UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon") {
|
||||
iconImage = generateImage(sourceImage.size, contextGenerator: { size, context in
|
||||
if let cgImage = sourceImage.cgImage {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, opaque: false)
|
||||
} else {
|
||||
iconImage = nil
|
||||
}
|
||||
case let .verified(fillColor, foregroundColor):
|
||||
if let backgroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconForeground") {
|
||||
iconImage = generateImage(backgroundImage.size, contextGenerator: { size, context in
|
||||
if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.saveGState()
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
|
||||
|
||||
context.setFillColor(fillColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.restoreGState()
|
||||
|
||||
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
|
||||
context.setFillColor(foregroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}, opaque: false)
|
||||
} else {
|
||||
iconImage = nil
|
||||
}
|
||||
case .fake:
|
||||
iconImage = nil
|
||||
case .scam:
|
||||
iconImage = nil
|
||||
case let .emojiStatus(emojiStatus, placeholderColor):
|
||||
iconImage = nil
|
||||
emojiFileId = emojiStatus.fileId
|
||||
emojiPlaceholderColor = placeholderColor
|
||||
|
||||
if case let .emojiStatus(previousEmojiStatus, _) = self.component?.content {
|
||||
if previousEmojiStatus.fileId != emojiStatus.fileId {
|
||||
self.emojiFileDisposable?.dispose()
|
||||
self.emojiFileDisposable = nil
|
||||
|
||||
self.emojiFile = nil
|
||||
|
||||
if let animationLayer = self.animationLayer {
|
||||
self.animationLayer = nil
|
||||
animationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak animationLayer] _ in
|
||||
animationLayer?.removeFromSuperlayer()
|
||||
})
|
||||
animationLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iconImage = self.iconView?.image
|
||||
if case let .emojiStatus(status, placeholderColor) = component.content {
|
||||
emojiFileId = status.fileId
|
||||
emojiPlaceholderColor = placeholderColor
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
var size = CGSize()
|
||||
|
||||
if let iconImage = iconImage {
|
||||
let iconView: UIImageView
|
||||
if let current = self.iconView {
|
||||
iconView = current
|
||||
} else {
|
||||
iconView = UIImageView()
|
||||
self.iconView = iconView
|
||||
self.addSubview(iconView)
|
||||
|
||||
iconView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
iconView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||
}
|
||||
iconView.image = iconImage
|
||||
size = iconImage.size.aspectFilled(availableSize)
|
||||
iconView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
} else {
|
||||
if let iconView = self.iconView {
|
||||
self.iconView = nil
|
||||
|
||||
iconView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak iconView] _ in
|
||||
iconView?.removeFromSuperview()
|
||||
})
|
||||
iconView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
if let emojiFileId = emojiFileId, let emojiPlaceholderColor = emojiPlaceholderColor {
|
||||
size = availableSize
|
||||
|
||||
if let emojiFile = self.emojiFile {
|
||||
self.emojiFileDisposable?.dispose()
|
||||
self.emojiFileDisposable = nil
|
||||
|
||||
let animationLayer: InlineStickerItemLayer
|
||||
if let current = self.animationLayer {
|
||||
animationLayer = current
|
||||
} else {
|
||||
animationLayer = InlineStickerItemLayer(
|
||||
context: component.context,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: emojiFile.fileId.id, file: emojiFile),
|
||||
file: emojiFile,
|
||||
cache: component.animationCache,
|
||||
renderer: component.animationRenderer,
|
||||
placeholderColor: emojiPlaceholderColor,
|
||||
pointSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
self.animationLayer = animationLayer
|
||||
self.layer.addSublayer(animationLayer)
|
||||
|
||||
animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
animationLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||
}
|
||||
animationLayer.frame = CGRect(origin: CGPoint(), size: size)
|
||||
animationLayer.isVisibleForAnimations = true
|
||||
} else {
|
||||
if self.emojiFileDisposable == nil {
|
||||
self.emojiFileDisposable = (component.context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.emojiFile = result[emojiFileId]
|
||||
strongSelf.state?.updated(transition: .immediate)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.emojiFileDisposable?.dispose()
|
||||
self.emojiFileDisposable = nil
|
||||
|
||||
if let animationLayer = self.animationLayer {
|
||||
self.animationLayer = nil
|
||||
|
||||
animationLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak animationLayer] _ in
|
||||
animationLayer?.removeFromSuperlayer()
|
||||
})
|
||||
animationLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "EmojiStatusSelectionComponent",
|
||||
module_name = "EmojiStatusSelectionComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||
"//submodules/Components/PagerComponent:PagerComponent",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,437 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EntityKeyboard
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import PagerComponent
|
||||
|
||||
public final class EmojiStatusSelectionComponent: Component {
|
||||
public typealias EnvironmentType = Empty
|
||||
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let emojiContent: EmojiPagerContentComponent
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
emojiContent: EmojiPagerContentComponent
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.emojiContent = emojiContent
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiStatusSelectionComponent, rhs: EmojiStatusSelectionComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings != rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.deviceMetrics != rhs.deviceMetrics {
|
||||
return false
|
||||
}
|
||||
if lhs.emojiContent != rhs.emojiContent {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let keyboardView: ComponentView<Empty>
|
||||
private let panelHostView: PagerExternalTopPanelContainer
|
||||
private let panelBackgroundView: BlurredBackgroundView
|
||||
private let panelSeparatorView: UIView
|
||||
|
||||
private var component: EmojiStatusSelectionComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.keyboardView = ComponentView<Empty>()
|
||||
self.panelHostView = PagerExternalTopPanelContainer()
|
||||
self.panelBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
||||
self.panelSeparatorView = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.layer.cornerRadius = 24.0
|
||||
|
||||
self.addSubview(self.panelBackgroundView)
|
||||
self.addSubview(self.panelSeparatorView)
|
||||
self.addSubview(self.panelHostView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: EmojiStatusSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
if self.component?.theme !== component.theme {
|
||||
self.backgroundColor = component.theme.list.plainBackgroundColor
|
||||
self.panelBackgroundView.updateColor(color: component.theme.list.plainBackgroundColor.withMultipliedAlpha(0.85), transition: .immediate)
|
||||
self.panelSeparatorView.backgroundColor = component.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
||||
let keyboardSize = self.keyboardView.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
containerInsets: UIEdgeInsets(top: 41.0 - 34.0, left: 0.0, bottom: 0.0, right: 0.0),
|
||||
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
||||
emojiContent: component.emojiContent,
|
||||
stickerContent: nil,
|
||||
gifContent: nil,
|
||||
hasRecentGifs: false,
|
||||
availableGifSearchEmojies: [],
|
||||
defaultToEmojiTab: true,
|
||||
externalTopPanelContainer: self.panelHostView,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
hideInputUpdated: { _, _, _ in },
|
||||
switchToTextInput: {},
|
||||
switchToGifSubject: { _ in },
|
||||
makeSearchContainerNode: { _ in return nil },
|
||||
deviceMetrics: component.deviceMetrics,
|
||||
hiddenInputHeight: 0.0,
|
||||
displayBottomPanel: false,
|
||||
isExpanded: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let keyboardComponentView = self.keyboardView.view {
|
||||
if keyboardComponentView.superview == nil {
|
||||
self.insertSubview(keyboardComponentView, at: 0)
|
||||
}
|
||||
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(), size: keyboardSize))
|
||||
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
||||
|
||||
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: 41.0)))
|
||||
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class EmojiStatusSelectionController: ViewController {
|
||||
private final class Node: ViewControllerTracingNode {
|
||||
private weak var controller: EmojiStatusSelectionController?
|
||||
private let context: AccountContext
|
||||
private weak var sourceView: UIView?
|
||||
private var globalSourceRect: CGRect?
|
||||
|
||||
private let componentHost: ComponentView<Empty>
|
||||
private let componentShadowLayer: SimpleLayer
|
||||
|
||||
private let cloudLayer0: SimpleLayer
|
||||
private let cloudShadowLayer0: SimpleLayer
|
||||
private let cloudLayer1: SimpleLayer
|
||||
private let cloudShadowLayer1: SimpleLayer
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var emojiContentDisposable: Disposable?
|
||||
private var emojiContent: EmojiPagerContentComponent?
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
|
||||
if let sourceView = sourceView {
|
||||
self.globalSourceRect = sourceView.convert(sourceView.bounds, to: nil)
|
||||
}
|
||||
|
||||
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.componentHost = ComponentView<Empty>()
|
||||
self.componentShadowLayer = SimpleLayer()
|
||||
self.componentShadowLayer.shadowOpacity = 0.15
|
||||
self.componentShadowLayer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
|
||||
self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
|
||||
self.componentShadowLayer.shadowRadius = 15.0
|
||||
|
||||
self.cloudLayer0 = SimpleLayer()
|
||||
self.cloudShadowLayer0 = SimpleLayer()
|
||||
self.cloudShadowLayer0.shadowOpacity = 0.15
|
||||
self.cloudShadowLayer0.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
|
||||
self.cloudShadowLayer0.shadowOffset = CGSize(width: 0.0, height: 2.0)
|
||||
self.cloudShadowLayer0.shadowRadius = 15.0
|
||||
|
||||
self.cloudLayer1 = SimpleLayer()
|
||||
self.cloudShadowLayer1 = SimpleLayer()
|
||||
self.cloudShadowLayer1.shadowOpacity = 0.15
|
||||
self.cloudShadowLayer1.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
|
||||
self.cloudShadowLayer1.shadowOffset = CGSize(width: 0.0, height: 2.0)
|
||||
self.cloudShadowLayer1.shadowRadius = 15.0
|
||||
|
||||
super.init()
|
||||
|
||||
self.layer.addSublayer(self.componentShadowLayer)
|
||||
self.layer.addSublayer(self.cloudShadowLayer0)
|
||||
self.layer.addSublayer(self.cloudShadowLayer1)
|
||||
|
||||
self.layer.addSublayer(self.cloudLayer0)
|
||||
self.layer.addSublayer(self.cloudLayer1)
|
||||
|
||||
self.emojiContentDisposable = (emojiContent
|
||||
|> deliverOnMainQueue).start(next: { [weak self] emojiContent in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.controller?._ready.set(.single(true))
|
||||
strongSelf.emojiContent = emojiContent
|
||||
|
||||
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { _, item, _, _, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.applyItem(item: item)
|
||||
},
|
||||
deleteBackwards: {
|
||||
},
|
||||
openStickerSettings: {
|
||||
},
|
||||
openFeatured: {
|
||||
},
|
||||
addGroupAction: { groupId, isPremiumLocked in
|
||||
|
||||
},
|
||||
clearGroup: { groupId in
|
||||
},
|
||||
pushController: { c in
|
||||
},
|
||||
presentController: { c in
|
||||
},
|
||||
presentGlobalOverlayController: { c in
|
||||
},
|
||||
navigationController: {
|
||||
return nil
|
||||
},
|
||||
sendSticker: nil,
|
||||
chatPeerId: nil
|
||||
)
|
||||
|
||||
strongSelf.refreshLayout(transition: .immediate)
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiContentDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func refreshLayout(transition: Transition) {
|
||||
guard let layout = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.containerLayoutUpdated(layout: layout, transition: transition)
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
self.componentShadowLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.componentHost.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
|
||||
self.cloudLayer0.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.cloudShadowLayer0.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.cloudLayer1.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.cloudShadowLayer1.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) {
|
||||
self.validLayout = layout
|
||||
|
||||
guard let emojiContent = self.emojiContent else {
|
||||
return
|
||||
}
|
||||
|
||||
self.cloudLayer0.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.cgColor
|
||||
self.cloudLayer1.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.cgColor
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
let componentSize = self.componentHost.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(EmojiStatusSelectionComponent(
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
emojiContent: emojiContent
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: layout.size.width - sideInset * 2.0, height: min(300.0, layout.size.height))
|
||||
)
|
||||
if let componentView = self.componentHost.view {
|
||||
var animateIn = false
|
||||
if componentView.superview == nil {
|
||||
self.view.addSubview(componentView)
|
||||
animateIn = true
|
||||
}
|
||||
|
||||
let sourceOrigin: CGPoint
|
||||
if let sourceView = self.sourceView {
|
||||
let sourceRect = sourceView.convert(sourceView.bounds, to: self.view)
|
||||
sourceOrigin = CGPoint(x: sourceRect.midX, y: sourceRect.maxY)
|
||||
} else if let globalSourceRect = self.globalSourceRect {
|
||||
let sourceRect = self.view.convert(globalSourceRect, from: nil)
|
||||
sourceOrigin = CGPoint(x: sourceRect.midX, y: sourceRect.maxY)
|
||||
} else {
|
||||
sourceOrigin = CGPoint(x: layout.size.width / 2.0, y: floor(layout.size.height / 2.0 - componentSize.height))
|
||||
}
|
||||
|
||||
let componentFrame = CGRect(origin: CGPoint(x: sideInset, y: sourceOrigin.y + 8.0), size: componentSize)
|
||||
|
||||
if self.componentShadowLayer.bounds.size != componentFrame.size {
|
||||
let componentShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: componentFrame.size), cornerRadius: 24.0).cgPath
|
||||
self.componentShadowLayer.shadowPath = componentShadowPath
|
||||
}
|
||||
transition.setFrame(layer: self.componentShadowLayer, frame: componentFrame)
|
||||
|
||||
let cloudOffset0: CGFloat = 30.0
|
||||
let cloudSize0: CGFloat = 16.0
|
||||
let cloudFrame0 = CGRect(origin: CGPoint(x: floor(sourceOrigin.x + cloudOffset0 - cloudSize0 / 2.0), y: componentFrame.minY - cloudSize0 / 2.0), size: CGSize(width: cloudSize0, height: cloudSize0))
|
||||
transition.setFrame(layer: self.cloudLayer0, frame: cloudFrame0)
|
||||
if self.cloudShadowLayer0.bounds.size != cloudFrame0.size {
|
||||
let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame0.size), cornerRadius: 24.0).cgPath
|
||||
self.cloudShadowLayer0.shadowPath = cloudShadowPath
|
||||
}
|
||||
transition.setFrame(layer: self.cloudShadowLayer0, frame: cloudFrame0)
|
||||
transition.setCornerRadius(layer: self.cloudLayer0, cornerRadius: cloudFrame0.width / 2.0)
|
||||
|
||||
let cloudOffset1 = CGPoint(x: -9.0, y: -14.0)
|
||||
let cloudSize1: CGFloat = 8.0
|
||||
let cloudFrame1 = CGRect(origin: CGPoint(x: floor(cloudFrame0.midX + cloudOffset1.x - cloudSize1 / 2.0), y: floor(cloudFrame0.midY + cloudOffset1.y - cloudSize1 / 2.0)), size: CGSize(width: cloudSize1, height: cloudSize1))
|
||||
transition.setFrame(layer: self.cloudLayer1, frame: cloudFrame1)
|
||||
if self.cloudShadowLayer1.bounds.size != cloudFrame1.size {
|
||||
let cloudShadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: cloudFrame1.size), cornerRadius: 24.0).cgPath
|
||||
self.cloudShadowLayer1.shadowPath = cloudShadowPath
|
||||
}
|
||||
transition.setFrame(layer: self.cloudShadowLayer1, frame: cloudFrame1)
|
||||
transition.setCornerRadius(layer: self.cloudLayer1, cornerRadius: cloudFrame1.width / 2.0)
|
||||
|
||||
transition.setFrame(view: componentView, frame: componentFrame)
|
||||
|
||||
if animateIn {
|
||||
self.allowsGroupOpacity = true
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.allowsGroupOpacity = false
|
||||
})
|
||||
|
||||
//componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.28)
|
||||
componentView.layer.animateScale(from: (componentView.bounds.width - 10.0) / componentView.bounds.width, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
//self.cloudShadowLayer0.animateAlpha(from: 0.0, to: 1.0, duration: 0.28)
|
||||
//self.cloudLayer0.animateAlpha(from: 0.0, to: 1.0, duration: 0.28)
|
||||
self.cloudLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.cloudShadowLayer0.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
//self.cloudShadowLayer1.animateAlpha(from: 0.0, to: 1.0, duration: 0.28)
|
||||
//self.cloudLayer1.animateAlpha(from: 0.0, to: 1.0, duration: 0.28)
|
||||
self.cloudLayer1.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.cloudShadowLayer1.animateScale(from: 0.01, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = super.hitTest(point, with: event) {
|
||||
if self.isDismissed {
|
||||
return self.view
|
||||
}
|
||||
|
||||
if result === self.view {
|
||||
self.isDismissed = true
|
||||
self.controller?.dismiss()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func applyItem(item: EmojiPagerContentComponent.Item?) {
|
||||
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.controller?.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private weak var sourceView: UIView?
|
||||
private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
|
||||
|
||||
fileprivate let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>) {
|
||||
self.context = context
|
||||
self.sourceView = sourceView
|
||||
self.emojiContent = emojiContent
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
}
|
||||
|
||||
required public init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
(self.displayNode as! Node).animateOut(completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(controller: self, context: self.context, sourceView: self.sourceView, emojiContent: self.emojiContent)
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
|
||||
}
|
||||
}
|
||||
|
|
@ -1619,6 +1619,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||
var width: CGFloat
|
||||
var headerInsets: UIEdgeInsets
|
||||
var itemInsets: UIEdgeInsets
|
||||
var curveNearBounds: Bool
|
||||
var itemGroupLayouts: [ItemGroupLayout]
|
||||
var itemDefaultHeaderHeight: CGFloat
|
||||
var itemFeaturedHeaderHeight: CGFloat
|
||||
|
|
@ -1642,6 +1643,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||
self.premiumButtonInset = 6.0
|
||||
self.premiumButtonHeight = 50.0
|
||||
|
||||
self.curveNearBounds = containerInsets.bottom <= 9.0
|
||||
|
||||
let minItemsPerRow: Int
|
||||
let minSpacing: CGFloat
|
||||
switch layoutType {
|
||||
|
|
@ -2258,6 +2261,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||
private var vibrancyEffectView: UIVisualEffectView?
|
||||
private let mirrorContentScrollView: UIView
|
||||
private let scrollView: ContentScrollView
|
||||
private var scrollGradientLayer: SimpleGradientLayer?
|
||||
private let boundsChangeTrackerLayer = SimpleLayer()
|
||||
private var effectiveVisibleSize: CGSize = CGSize()
|
||||
|
||||
|
|
@ -3562,6 +3566,29 @@ public final class EmojiPagerContentComponent: Component {
|
|||
let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
itemTransition.setPosition(layer: itemLayer, position: itemPosition)
|
||||
|
||||
if itemLayout.curveNearBounds {
|
||||
let fractionLength: CGFloat = itemFrame.height
|
||||
let rotationX: CGFloat = 1.0 - max(0.0, min(1.0, (effectiveVisibleBounds.maxY + 4.0 + itemFrame.height / 2.0 - itemPosition.y) / fractionLength))
|
||||
if rotationX.isZero {
|
||||
itemTransition.setTransform(layer: itemLayer, transform: CATransform3DIdentity)
|
||||
} else {
|
||||
var transform = CATransform3DIdentity
|
||||
|
||||
let centralOffset = itemFrame.midX - effectiveVisibleBounds.width / 2.0
|
||||
transform = CATransform3DTranslate(transform, centralOffset * 2.0, 0.0, 0.0)
|
||||
|
||||
var projectionMatrix = CATransform3DIdentity
|
||||
projectionMatrix.m34 = -1.0 / 500.0
|
||||
transform = CATransform3DConcat(projectionMatrix, transform)
|
||||
|
||||
transform = CATransform3DTranslate(transform, -centralOffset * 2.0, 0.0, 0.0)
|
||||
|
||||
transform = CATransform3DTranslate(transform, 0.0, -rotationX * itemFrame.height * 0.5, 0.0)
|
||||
transform = CATransform3DRotate(transform, CGFloat.pi * 0.5 * rotationX, 1.0, 0.0, 0.0)
|
||||
itemTransition.setTransform(layer: itemLayer, transform: transform)
|
||||
}
|
||||
}
|
||||
|
||||
var badge: ItemLayer.Badge?
|
||||
if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker {
|
||||
badge = .premium
|
||||
|
|
@ -3764,6 +3791,44 @@ public final class EmojiPagerContentComponent: Component {
|
|||
self.updateShimmerIfNeeded()
|
||||
}
|
||||
|
||||
if itemLayout.curveNearBounds {
|
||||
let scrollGradientLayer: SimpleGradientLayer
|
||||
if let current = self.scrollGradientLayer {
|
||||
scrollGradientLayer = current
|
||||
} else {
|
||||
scrollGradientLayer = SimpleGradientLayer()
|
||||
self.scrollGradientLayer = scrollGradientLayer
|
||||
|
||||
var locations: [NSNumber] = []
|
||||
var colors: [CGColor] = []
|
||||
let numStops = 6
|
||||
for i in 0 ..< numStops {
|
||||
let step = CGFloat(i) / CGFloat(numStops - 1)
|
||||
locations.append(step as NSNumber)
|
||||
colors.append(keyboardChildEnvironment.theme.list.plainBackgroundColor.withAlphaComponent(step * step).cgColor)
|
||||
}
|
||||
|
||||
scrollGradientLayer.locations = locations
|
||||
scrollGradientLayer.colors = colors
|
||||
scrollGradientLayer.type = .axial
|
||||
|
||||
self.layer.addSublayer(scrollGradientLayer)
|
||||
}
|
||||
|
||||
let gradientHeight: CGFloat = 20.0
|
||||
transition.setFrame(layer: scrollGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: effectiveVisibleBounds.height - gradientHeight), size: CGSize(width: effectiveVisibleBounds.width, height: gradientHeight)))
|
||||
//let fractionLength: CGFloat = 20.0
|
||||
//transition.setAlpha(layer: scrollGradientLayer, alpha: max(0.0, min(1.0, (itemLayout.contentSize.height - effectiveVisibleBounds.maxY) / fractionLength)))
|
||||
|
||||
scrollGradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
|
||||
scrollGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
|
||||
} else {
|
||||
if let scrollGradientLayer = self.scrollGradientLayer {
|
||||
self.scrollGradientLayer = nil
|
||||
scrollGradientLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
if let topVisibleGroupId = topVisibleGroupId {
|
||||
self.activeItemUpdated?.invoke((topVisibleGroupId, topVisibleSubgroupId, .immediate))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import PagerComponent
|
|||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import BlurredBackgroundComponent
|
||||
import BundleIconComponent
|
||||
import AudioToolbox
|
||||
import SwiftSignalKit
|
||||
|
|
@ -83,6 +82,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let containerInsets: UIEdgeInsets
|
||||
public let topPanelInsets: UIEdgeInsets
|
||||
public let emojiContent: EmojiPagerContentComponent
|
||||
public let stickerContent: EmojiPagerContentComponent?
|
||||
public let gifContent: GifPagerContentComponent?
|
||||
|
|
@ -97,12 +97,14 @@ public final class EntityKeyboardComponent: Component {
|
|||
public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode?
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let hiddenInputHeight: CGFloat
|
||||
public let displayBottomPanel: Bool
|
||||
public let isExpanded: Bool
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
containerInsets: UIEdgeInsets,
|
||||
topPanelInsets: UIEdgeInsets,
|
||||
emojiContent: EmojiPagerContentComponent,
|
||||
stickerContent: EmojiPagerContentComponent?,
|
||||
gifContent: GifPagerContentComponent?,
|
||||
|
|
@ -117,11 +119,13 @@ public final class EntityKeyboardComponent: Component {
|
|||
makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode?,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
hiddenInputHeight: CGFloat,
|
||||
displayBottomPanel: Bool,
|
||||
isExpanded: Bool
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.containerInsets = containerInsets
|
||||
self.topPanelInsets = topPanelInsets
|
||||
self.emojiContent = emojiContent
|
||||
self.stickerContent = stickerContent
|
||||
self.gifContent = gifContent
|
||||
|
|
@ -136,6 +140,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
self.makeSearchContainerNode = makeSearchContainerNode
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.hiddenInputHeight = hiddenInputHeight
|
||||
self.displayBottomPanel = displayBottomPanel
|
||||
self.isExpanded = isExpanded
|
||||
}
|
||||
|
||||
|
|
@ -149,6 +154,9 @@ public final class EntityKeyboardComponent: Component {
|
|||
if lhs.containerInsets != rhs.containerInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.topPanelInsets != rhs.topPanelInsets {
|
||||
return false
|
||||
}
|
||||
if lhs.emojiContent != rhs.emojiContent {
|
||||
return false
|
||||
}
|
||||
|
|
@ -176,6 +184,9 @@ public final class EntityKeyboardComponent: Component {
|
|||
if lhs.hiddenInputHeight != rhs.hiddenInputHeight {
|
||||
return false
|
||||
}
|
||||
if lhs.displayBottomPanel != rhs.displayBottomPanel {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
|
|
@ -247,6 +258,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
icon: .recent,
|
||||
theme: component.theme,
|
||||
useAccentColor: !component.displayBottomPanel,
|
||||
title: component.strings.Stickers_Recent,
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.recent)
|
||||
|
|
@ -260,6 +272,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
icon: .trending,
|
||||
theme: component.theme,
|
||||
useAccentColor: !component.displayBottomPanel,
|
||||
title: component.strings.Stickers_Trending,
|
||||
pressed: { [weak self] in
|
||||
self?.component?.switchToGifSubject(.trending)
|
||||
|
|
@ -297,7 +310,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
contentTopPanels.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topGifItems,
|
||||
containerSideInset: component.containerInsets.left,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
forceActiveItemId: defaultActiveGifItemId,
|
||||
activeContentItemIdUpdated: gifsContentItemIdUpdated,
|
||||
reorderItems: { _ in
|
||||
|
|
@ -325,6 +338,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
icon: .featured,
|
||||
theme: component.theme,
|
||||
useAccentColor: !component.displayBottomPanel,
|
||||
title: component.strings.Stickers_Trending,
|
||||
pressed: { [weak self] in
|
||||
self?.component?.stickerContent?.inputInteractionHolder.inputInteraction?.openFeatured()
|
||||
|
|
@ -368,6 +382,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
icon: icon,
|
||||
theme: component.theme,
|
||||
useAccentColor: !component.displayBottomPanel,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "stickers", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
|
|
@ -404,7 +419,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
contentTopPanels.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topStickerItems,
|
||||
containerSideInset: component.containerInsets.left,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
defaultActiveItemId: stickerContent.itemGroups.first?.groupId,
|
||||
activeContentItemIdUpdated: stickersContentItemIdUpdated,
|
||||
reorderItems: { [weak self] items in
|
||||
|
|
@ -457,6 +472,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
content: AnyComponent(EntityKeyboardIconTopPanelComponent(
|
||||
icon: icon,
|
||||
theme: component.theme,
|
||||
useAccentColor: !component.displayBottomPanel,
|
||||
title: title,
|
||||
pressed: { [weak self] in
|
||||
self?.scrollToItemGroup(contentId: "emoji", groupId: itemGroup.supergroupId, subgroupId: nil)
|
||||
|
|
@ -506,7 +522,7 @@ public final class EntityKeyboardComponent: Component {
|
|||
contentTopPanels.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardTopPanelComponent(
|
||||
theme: component.theme,
|
||||
items: topEmojiItems,
|
||||
containerSideInset: component.containerInsets.left,
|
||||
containerSideInset: component.containerInsets.left + component.topPanelInsets.left,
|
||||
activeContentItemIdUpdated: emojiContentItemIdUpdated,
|
||||
reorderItems: { [weak self] items in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -571,14 +587,14 @@ public final class EntityKeyboardComponent: Component {
|
|||
displayBackground: component.externalTopPanelContainer == nil
|
||||
)),
|
||||
externalTopPanelContainer: component.externalTopPanelContainer,
|
||||
bottomPanel: AnyComponent(EntityKeyboardBottomPanelComponent(
|
||||
bottomPanel: component.displayBottomPanel ? AnyComponent(EntityKeyboardBottomPanelComponent(
|
||||
theme: component.theme,
|
||||
containerInsets: component.containerInsets,
|
||||
deleteBackwards: { [weak self] in
|
||||
self?.component?.emojiContent.inputInteractionHolder.inputInteraction?.deleteBackwards()
|
||||
AudioServicesPlaySystemSound(0x451)
|
||||
}
|
||||
)),
|
||||
)) : nil,
|
||||
panelStateUpdated: { [weak self] panelState, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -256,17 +256,20 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
|||
|
||||
let icon: Icon
|
||||
let theme: PresentationTheme
|
||||
let useAccentColor: Bool
|
||||
let title: String
|
||||
let pressed: () -> Void
|
||||
|
||||
init(
|
||||
icon: Icon,
|
||||
theme: PresentationTheme,
|
||||
useAccentColor: Bool,
|
||||
title: String,
|
||||
pressed: @escaping () -> Void
|
||||
) {
|
||||
self.icon = icon
|
||||
self.theme = theme
|
||||
self.useAccentColor = useAccentColor
|
||||
self.title = title
|
||||
self.pressed = pressed
|
||||
}
|
||||
|
|
@ -278,6 +281,9 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
|||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.useAccentColor != rhs.useAccentColor {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
|
|
@ -352,7 +358,16 @@ final class EntityKeyboardIconTopPanelComponent: Component {
|
|||
|
||||
self.component = component
|
||||
|
||||
let color = itemEnvironment.isHighlighted ? component.theme.chat.inputMediaPanel.panelHighlightedIconColor : component.theme.chat.inputMediaPanel.panelIconColor
|
||||
let color: UIColor
|
||||
if itemEnvironment.isHighlighted {
|
||||
if component.useAccentColor {
|
||||
color = component.theme.list.itemAccentColor
|
||||
} else {
|
||||
color = component.theme.chat.inputMediaPanel.panelHighlightedIconColor
|
||||
}
|
||||
} else {
|
||||
color = component.theme.chat.inputMediaPanel.panelIconColor
|
||||
}
|
||||
|
||||
if self.iconView.tintColor != color {
|
||||
if !transition.animation.isImmediate {
|
||||
|
|
|
|||
|
|
@ -3788,7 +3788,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
return true
|
||||
}
|
||||
|
||||
self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder)
|
||||
self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
|
||||
self.navigationItem.titleView = self.chatTitleView
|
||||
self.chatTitleView?.pressed = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
return hasPremium
|
||||
}
|
||||
|
||||
static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||
static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool, areUnicodeEmojiEnabled: Bool, areCustomEmojiEnabled: Bool, chatPeerId: EnginePeer.Id?) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||
|
||||
|
|
@ -178,21 +178,23 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
}
|
||||
}
|
||||
|
||||
for (subgroupId, list) in staticEmojiMapping {
|
||||
let groupId: AnyHashable = "static"
|
||||
for emojiString in list {
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: nil,
|
||||
itemFile: nil,
|
||||
staticEmoji: emojiString,
|
||||
subgroupId: subgroupId.rawValue
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
if areUnicodeEmojiEnabled {
|
||||
for (subgroupId, list) in staticEmojiMapping {
|
||||
let groupId: AnyHashable = "static"
|
||||
for emojiString in list {
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: nil,
|
||||
itemFile: nil,
|
||||
staticEmoji: emojiString,
|
||||
subgroupId: subgroupId.rawValue
|
||||
)
|
||||
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -378,7 +380,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
animationRenderer = MultiAnimationRendererImpl()
|
||||
//}
|
||||
|
||||
let emojiItems = emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
|
||||
let emojiItems = emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
|
||||
|
||||
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
|
||||
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers]
|
||||
|
|
@ -1759,6 +1761,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
theme: interfaceState.theme,
|
||||
strings: interfaceState.strings,
|
||||
containerInsets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: bottomInset, right: rightInset),
|
||||
topPanelInsets: UIEdgeInsets(),
|
||||
emojiContent: self.currentInputData.emoji,
|
||||
stickerContent: stickerContent,
|
||||
gifContent: gifContent?.component,
|
||||
|
|
@ -1822,6 +1825,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||
},
|
||||
deviceMetrics: deviceMetrics,
|
||||
hiddenInputHeight: hiddenInputHeight,
|
||||
displayBottomPanel: true,
|
||||
isExpanded: isExpanded
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -2270,7 +2274,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
|||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var emojiComponent: EmojiPagerContentComponent?
|
||||
let _ = ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil).start(next: { value in
|
||||
let _ = ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil).start(next: { value in
|
||||
emojiComponent = value
|
||||
semaphore.signal()
|
||||
})
|
||||
|
|
@ -2285,7 +2289,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
|||
gifs: nil,
|
||||
availableGifSearchEmojies: []
|
||||
),
|
||||
updatedInputData: ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in
|
||||
updatedInputData: ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in
|
||||
return ChatEntityKeyboardInputNode.InputData(
|
||||
emoji: emojiComponent,
|
||||
stickers: nil,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ import PhoneNumberFormat
|
|||
import ChatTitleActivityNode
|
||||
import AnimatedCountLabelNode
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
|
||||
private let subtitleFont = Font.regular(13.0)
|
||||
|
|
@ -38,12 +42,13 @@ private enum ChatTitleIcon {
|
|||
case mute
|
||||
}
|
||||
|
||||
private enum ChatTitleCredibilityIcon {
|
||||
private enum ChatTitleCredibilityIcon: Equatable {
|
||||
case none
|
||||
case fake
|
||||
case scam
|
||||
case verified
|
||||
case premium
|
||||
case emojiStatus(PeerEmojiStatus)
|
||||
}
|
||||
|
||||
final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
|
|
@ -54,12 +59,14 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
private var strings: PresentationStrings
|
||||
private var dateTimeFormat: PresentationDateTimeFormat
|
||||
private var nameDisplayOrder: PresentationPersonNameOrder
|
||||
private let animationCache: AnimationCache
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
private let contentContainer: ASDisplayNode
|
||||
let titleNode: ImmediateAnimatedCountLabelNode
|
||||
let titleLeftIconNode: ASImageNode
|
||||
let titleRightIconNode: ASImageNode
|
||||
let titleCredibilityIconNode: ASImageNode
|
||||
let titleCredibilityIconView: ComponentHostView<Empty>
|
||||
let activityNode: ChatTitleActivityNode
|
||||
|
||||
private let button: HighlightTrackingButtonNode
|
||||
|
|
@ -142,7 +149,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
}
|
||||
if peer.id != self.context.account.peerId {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
if peer.isFake {
|
||||
if let _ = peerView.cachedData as? CachedUserData, peer.isPremium && !premiumConfiguration.isPremiumDisabled, "".isEmpty {
|
||||
titleCredibilityIcon = .emojiStatus(PeerEmojiStatus(fileId: 5431449001532594346))
|
||||
} else if peer.isFake {
|
||||
titleCredibilityIcon = .fake
|
||||
} else if peer.isScam {
|
||||
titleCredibilityIcon = .scam
|
||||
|
|
@ -261,7 +270,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
|
||||
if titleCredibilityIcon != self.titleCredibilityIcon {
|
||||
self.titleCredibilityIcon = titleCredibilityIcon
|
||||
switch titleCredibilityIcon {
|
||||
/*switch titleCredibilityIcon {
|
||||
case .none:
|
||||
self.titleCredibilityIconNode.image = nil
|
||||
case .fake:
|
||||
|
|
@ -272,7 +281,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
self.titleCredibilityIconNode.image = PresentationResourcesChatList.verifiedIcon(titleTheme)
|
||||
case .premium:
|
||||
self.titleCredibilityIconNode.image = PresentationResourcesChatList.premiumIcon(titleTheme)
|
||||
}
|
||||
}*/
|
||||
updated = true
|
||||
}
|
||||
|
||||
|
|
@ -543,12 +552,14 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
}
|
||||
}
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) {
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
|
||||
self.contentContainer = ASDisplayNode()
|
||||
|
||||
|
|
@ -564,10 +575,8 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
self.titleRightIconNode.displayWithoutProcessing = true
|
||||
self.titleRightIconNode.displaysAsynchronously = false
|
||||
|
||||
self.titleCredibilityIconNode = ASImageNode()
|
||||
self.titleCredibilityIconNode.isLayerBacked = true
|
||||
self.titleCredibilityIconNode.displayWithoutProcessing = true
|
||||
self.titleCredibilityIconNode.displaysAsynchronously = false
|
||||
self.titleCredibilityIconView = ComponentHostView()
|
||||
self.titleCredibilityIconView.isUserInteractionEnabled = false
|
||||
|
||||
self.activityNode = ChatTitleActivityNode()
|
||||
self.button = HighlightTrackingButtonNode()
|
||||
|
|
@ -592,13 +601,14 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
if highlighted {
|
||||
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.activityNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.titleCredibilityIconNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.titleCredibilityIconView.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.titleNode.alpha = 0.4
|
||||
strongSelf.activityNode.alpha = 0.4
|
||||
strongSelf.titleCredibilityIconView.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.titleNode.alpha = 1.0
|
||||
strongSelf.activityNode.alpha = 1.0
|
||||
strongSelf.titleCredibilityIconNode.alpha = 1.0
|
||||
strongSelf.titleCredibilityIconView.alpha = 1.0
|
||||
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
|
@ -653,13 +663,43 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
self.titleLeftIconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let image = self.titleCredibilityIconNode.image {
|
||||
if self.titleCredibilityIconNode.supernode == nil {
|
||||
self.titleNode.addSubnode(self.titleCredibilityIconNode)
|
||||
let titleCredibilityContent: EmojiStatusComponent.Content
|
||||
switch self.titleCredibilityIcon {
|
||||
case .none:
|
||||
titleCredibilityContent = .none
|
||||
case .premium:
|
||||
titleCredibilityContent = .premium(color: self.theme.list.itemAccentColor)
|
||||
case .verified:
|
||||
titleCredibilityContent = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor)
|
||||
case .fake:
|
||||
titleCredibilityContent = .fake(color: self.theme.chat.message.incoming.scamColor)
|
||||
case .scam:
|
||||
titleCredibilityContent = .scam(color: self.theme.chat.message.incoming.scamColor)
|
||||
case let .emojiStatus(emojiStatus):
|
||||
titleCredibilityContent = .emojiStatus(status: emojiStatus, placeholderColor: self.theme.list.mediaPlaceholderColor)
|
||||
}
|
||||
|
||||
let titleCredibilitySize = self.titleCredibilityIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: self.context,
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
content: titleCredibilityContent,
|
||||
action: nil,
|
||||
longTapAction: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 20.0, height: 20.0)
|
||||
)
|
||||
|
||||
if self.titleCredibilityIcon != .none {
|
||||
self.titleNode.view.addSubview(self.titleCredibilityIconView)
|
||||
credibilityIconWidth = titleCredibilitySize.width + 3.0
|
||||
} else {
|
||||
if self.titleCredibilityIconView.superview != nil {
|
||||
self.titleCredibilityIconView.removeFromSuperview()
|
||||
}
|
||||
credibilityIconWidth = image.size.width + 3.0
|
||||
} else if self.titleCredibilityIconNode.supernode != nil {
|
||||
self.titleCredibilityIconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let image = self.titleRightIconNode.image {
|
||||
|
|
@ -705,13 +745,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
if let image = self.titleLeftIconNode.image {
|
||||
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size)
|
||||
}
|
||||
if let image = self.titleCredibilityIconNode.image {
|
||||
var originY: CGFloat = 3.0
|
||||
if [.fake, .scam].contains(self.titleCredibilityIcon) {
|
||||
originY = 2.0
|
||||
}
|
||||
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width - image.size.width, y: originY), size: image.size)
|
||||
}
|
||||
|
||||
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.width - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
|
||||
|
||||
if let image = self.titleRightIconNode.image {
|
||||
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size)
|
||||
}
|
||||
|
|
@ -729,13 +765,9 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
if let image = self.titleLeftIconNode.image {
|
||||
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size)
|
||||
}
|
||||
if let image = self.titleCredibilityIconNode.image {
|
||||
var originY: CGFloat = titleFrame.minY + 7.0
|
||||
if [.fake, .scam].contains(self.titleCredibilityIcon) {
|
||||
originY = titleFrame.minY + 6.0
|
||||
}
|
||||
self.titleCredibilityIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: originY), size: image.size)
|
||||
}
|
||||
|
||||
self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize)
|
||||
|
||||
if let image = self.titleRightIconNode.image {
|
||||
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size)
|
||||
}
|
||||
|
|
@ -744,8 +776,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||
self.pointerInteraction = PointerInteraction(view: self, style: .rectangle(CGSize(width: titleFrame.width + 16.0, height: 40.0)))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed?()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ import PeerInfoAvatarListNode
|
|||
import AnimationUI
|
||||
import ContextUI
|
||||
import ManagedAnimationNode
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
enum PeerInfoHeaderButtonKey: Hashable {
|
||||
case message
|
||||
|
|
@ -1985,8 +1990,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let titleNodeContainer: ASDisplayNode
|
||||
let titleNodeRawContainer: ASDisplayNode
|
||||
let titleNode: MultiScaleTextNode
|
||||
let titleCredibilityIconNode: ASImageNode
|
||||
let titleExpandedCredibilityIconNode: ASImageNode
|
||||
let titleCredibilityIconView: ComponentHostView<Empty>
|
||||
var credibilityIconSize: CGSize?
|
||||
let titleExpandedCredibilityIconView: ComponentHostView<Empty>
|
||||
var titleExpandedCredibilityIconSize: CGSize?
|
||||
let subtitleNodeContainer: ASDisplayNode
|
||||
let subtitleNodeRawContainer: ASDisplayNode
|
||||
let subtitleNode: MultiScaleTextNode
|
||||
|
|
@ -2023,6 +2030,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
var backgroundAlpha: CGFloat = 1.0
|
||||
var updateHeaderAlpha: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool) {
|
||||
self.context = context
|
||||
self.isAvatarExpanded = avatarInitiallyExpanded
|
||||
|
|
@ -2037,15 +2047,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.titleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.titleCredibilityIconNode = ASImageNode()
|
||||
self.titleCredibilityIconNode.displaysAsynchronously = false
|
||||
self.titleCredibilityIconNode.displayWithoutProcessing = true
|
||||
self.titleNode.stateNode(forKey: TitleNodeStateRegular)?.addSubnode(self.titleCredibilityIconNode)
|
||||
self.titleCredibilityIconView = ComponentHostView<Empty>()
|
||||
self.titleNode.stateNode(forKey: TitleNodeStateRegular)?.view.addSubview(self.titleCredibilityIconView)
|
||||
|
||||
self.titleExpandedCredibilityIconNode = ASImageNode()
|
||||
self.titleExpandedCredibilityIconNode.displaysAsynchronously = false
|
||||
self.titleExpandedCredibilityIconNode.displayWithoutProcessing = true
|
||||
self.titleNode.stateNode(forKey: TitleNodeStateExpanded)?.addSubnode(self.titleExpandedCredibilityIconNode)
|
||||
self.titleExpandedCredibilityIconView = ComponentHostView<Empty>()
|
||||
self.titleNode.stateNode(forKey: TitleNodeStateExpanded)?.view.addSubview(self.titleExpandedCredibilityIconView)
|
||||
|
||||
self.subtitleNodeContainer = ASDisplayNode()
|
||||
self.subtitleNodeRawContainer = ASDisplayNode()
|
||||
|
|
@ -2099,6 +2105,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
|
||||
super.init()
|
||||
|
||||
requestUpdateLayoutImpl = { [weak self] in
|
||||
|
|
@ -2183,11 +2194,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let phoneGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handlePhoneLongPress(_:)))
|
||||
self.subtitleNodeRawContainer.view.addGestureRecognizer(phoneGestureRecognizer)
|
||||
|
||||
let premiumGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleStarTap(_:)))
|
||||
/*let premiumGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleStarTap(_:)))
|
||||
self.titleCredibilityIconNode.view.addGestureRecognizer(premiumGestureRecognizer)
|
||||
|
||||
let expandedPremiumGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleStarTap(_:)))
|
||||
self.titleExpandedCredibilityIconNode.view.addGestureRecognizer(expandedPremiumGestureRecognizer)
|
||||
self.titleExpandedCredibilityIconNode.view.addGestureRecognizer(expandedPremiumGestureRecognizer)*/
|
||||
}
|
||||
|
||||
@objc private func handleUsernameLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
|
|
@ -2202,12 +2213,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func handleStarTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
/*@objc private func handleStarTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
guard let view = gestureRecognizer.view, self.currentCredibilityIcon == .premium else {
|
||||
return
|
||||
}
|
||||
self.displayPremiumIntro?(view, view == self.titleExpandedCredibilityIconNode.view)
|
||||
}
|
||||
self.displayPremiumIntro?(view, view == self.titleExpandedCredibilityIconView.componentView)
|
||||
}*/
|
||||
|
||||
func initiateAvatarExpansion(gallery: Bool, first: Bool) {
|
||||
if let peer = self.peer, peer.profileImageRepresentations.isEmpty && gallery {
|
||||
|
|
@ -2265,12 +2276,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
self.avatarListNode.listContainerNode.updateEntryIsHidden(entry: entry)
|
||||
}
|
||||
|
||||
private enum CredibilityIcon {
|
||||
private enum CredibilityIcon: Equatable {
|
||||
case none
|
||||
case premium
|
||||
case verified
|
||||
case fake
|
||||
case scam
|
||||
case emojiStatus(PeerEmojiStatus)
|
||||
}
|
||||
|
||||
private var currentCredibilityIcon: CredibilityIcon?
|
||||
|
|
@ -2303,7 +2315,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
let credibilityIcon: CredibilityIcon
|
||||
if let peer = peer {
|
||||
if let cachedData = cachedData as? CachedUserData, let emojiStatus = cachedData.emojiStatus {
|
||||
credibilityIcon = .emojiStatus(emojiStatus)
|
||||
} else if let peer = peer {
|
||||
if peer.isFake {
|
||||
credibilityIcon = .fake
|
||||
} else if peer.isScam {
|
||||
|
|
@ -2321,6 +2335,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
|
||||
if themeUpdated || self.currentCredibilityIcon != credibilityIcon {
|
||||
self.currentCredibilityIcon = credibilityIcon
|
||||
|
||||
let image: UIImage?
|
||||
var expandedImage: UIImage?
|
||||
|
||||
|
|
@ -2388,8 +2403,92 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
image = nil
|
||||
}
|
||||
|
||||
self.titleCredibilityIconNode.image = image
|
||||
self.titleExpandedCredibilityIconNode.image = expandedImage ?? image
|
||||
let _ = image
|
||||
let _ = expandedImage
|
||||
|
||||
let emojiRegularStatusContent: EmojiStatusComponent.Content
|
||||
let emojiExpandedStatusContent: EmojiStatusComponent.Content
|
||||
switch credibilityIcon {
|
||||
case .none:
|
||||
emojiRegularStatusContent = .none
|
||||
emojiExpandedStatusContent = .none
|
||||
case .premium:
|
||||
emojiRegularStatusContent = .premium(color: presentationData.theme.list.itemAccentColor)
|
||||
emojiExpandedStatusContent = .premium(color: UIColor(rgb: 0xffffff, alpha: 0.75))
|
||||
case .verified:
|
||||
emojiRegularStatusContent = .verified(fillColor: presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
emojiExpandedStatusContent = .verified(fillColor: UIColor(rgb: 0xffffff, alpha: 0.75), foregroundColor: .clear)
|
||||
case .fake:
|
||||
emojiRegularStatusContent = .fake(color: presentationData.theme.chat.message.incoming.scamColor)
|
||||
emojiExpandedStatusContent = .fake(color: presentationData.theme.chat.message.incoming.scamColor)
|
||||
case .scam:
|
||||
emojiRegularStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor)
|
||||
emojiExpandedStatusContent = .scam(color: presentationData.theme.chat.message.incoming.scamColor)
|
||||
case let .emojiStatus(emojiStatus):
|
||||
emojiRegularStatusContent = .emojiStatus(status: emojiStatus, placeholderColor: presentationData.theme.list.mediaPlaceholderColor)
|
||||
emojiExpandedStatusContent = .emojiStatus(status: emojiStatus, placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.15))
|
||||
}
|
||||
|
||||
let iconSize = self.titleCredibilityIconView.update(
|
||||
transition: Transition(transition),
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: self.context,
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
content: emojiRegularStatusContent,
|
||||
action: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, false)
|
||||
},
|
||||
longTapAction: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.context.engine.accountData.setEmojiStatus(file: nil).start()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
let expandedIconSize = self.titleExpandedCredibilityIconView.update(
|
||||
transition: Transition(transition),
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: self.context,
|
||||
animationCache: self.animationCache,
|
||||
animationRenderer: self.animationRenderer,
|
||||
content: emojiExpandedStatusContent,
|
||||
action: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, false)
|
||||
},
|
||||
longTapAction: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.context.engine.accountData.setEmojiStatus(file: nil).start()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
|
||||
self.credibilityIconSize = iconSize
|
||||
self.titleExpandedCredibilityIconSize = expandedIconSize
|
||||
|
||||
/*if let image = image {
|
||||
self.credibilityIconSize = image.size
|
||||
self.titleExpandedCredibilityIconSize = (expandedImage ?? image).size
|
||||
} else {
|
||||
self.credibilityIconSize = nil
|
||||
self.titleExpandedCredibilityIconSize = nil
|
||||
}*/
|
||||
|
||||
//self.titleCredibilityIconNode.image = image
|
||||
//self.titleExpandedCredibilityIconNode.image = expandedImage ?? image
|
||||
}
|
||||
|
||||
self.regularContentNode.alpha = state.isEditing ? 0.0 : 1.0
|
||||
|
|
@ -2610,10 +2709,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size
|
||||
|
||||
var titleHorizontalOffset: CGFloat = 0.0
|
||||
if let image = self.titleCredibilityIconNode.image {
|
||||
titleHorizontalOffset = -(image.size.width + 4.0) / 2.0
|
||||
transition.updateFrame(node: self.titleCredibilityIconNode, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0, y: floor((titleSize.height - image.size.height) / 2.0) + 1.0), size: image.size))
|
||||
transition.updateFrame(node: self.titleExpandedCredibilityIconNode, frame: CGRect(origin: CGPoint(x: titleExpandedSize.width + 4.0, y: floor((titleExpandedSize.height - image.size.height) / 2.0) + 1.0), size: image.size))
|
||||
if let credibilityIconSize = self.credibilityIconSize, let titleExpandedCredibilityIconSize = self.titleExpandedCredibilityIconSize {
|
||||
titleHorizontalOffset = -(credibilityIconSize.width + 4.0) / 2.0
|
||||
transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0, y: floor((titleSize.height - credibilityIconSize.height) / 2.0) + 1.0), size: credibilityIconSize))
|
||||
transition.updateFrame(view: self.titleExpandedCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleExpandedSize.width + 4.0, y: floor((titleExpandedSize.height - titleExpandedCredibilityIconSize.height) / 2.0) + 1.0), size: titleExpandedCredibilityIconSize))
|
||||
}
|
||||
|
||||
var titleFrame: CGRect
|
||||
|
|
@ -3098,13 +3197,18 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||
return nil
|
||||
}
|
||||
|
||||
if self.currentCredibilityIcon == .premium && !(self.state?.isEditing ?? false) {
|
||||
let iconFrame = self.titleCredibilityIconNode.view.convert(self.titleCredibilityIconNode.bounds, to: self.view)
|
||||
let expandedIconFrame = self.titleExpandedCredibilityIconNode.view.convert(self.titleExpandedCredibilityIconNode.bounds, to: self.view)
|
||||
if expandedIconFrame.contains(point) && self.isAvatarExpanded {
|
||||
return self.titleExpandedCredibilityIconNode.view
|
||||
} else if iconFrame.contains(point) {
|
||||
return self.titleCredibilityIconNode.view
|
||||
if !(self.state?.isEditing ?? false) {
|
||||
switch self.currentCredibilityIcon {
|
||||
case .premium, .emojiStatus:
|
||||
let iconFrame = self.titleCredibilityIconView.convert(self.titleCredibilityIconView.bounds, to: self.view)
|
||||
let expandedIconFrame = self.titleExpandedCredibilityIconView.convert(self.titleExpandedCredibilityIconView.bounds, to: self.view)
|
||||
if expandedIconFrame.contains(point) && self.isAvatarExpanded {
|
||||
return self.titleExpandedCredibilityIconView.hitTest(self.view.convert(point, to: self.titleExpandedCredibilityIconView), with: event)
|
||||
} else if iconFrame.contains(point) {
|
||||
return self.titleCredibilityIconView.hitTest(self.view.convert(point, to: self.titleCredibilityIconView), with: event)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ import CreateExternalMediaStreamScreen
|
|||
import PaymentMethodUI
|
||||
import PremiumUI
|
||||
import InstantPageCache
|
||||
import EmojiStatusSelectionComponent
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
protocol PeerInfoScreenItem: AnyObject {
|
||||
var id: AnyHashable { get }
|
||||
|
|
@ -3025,6 +3028,30 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||
}
|
||||
}))
|
||||
}
|
||||
|
||||
self.headerNode.displayPremiumIntro = { [weak self] sourceView, white in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
let animationRenderer = MultiAnimationRendererImpl()
|
||||
strongSelf.controller?.present(EmojiStatusSelectionController(
|
||||
context: strongSelf.context,
|
||||
sourceView: sourceView,
|
||||
emojiContent: ChatEntityKeyboardInputNode.emojiInputData(
|
||||
context: strongSelf.context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
isStandalone: false,
|
||||
areUnicodeEmojiEnabled: false,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: strongSelf.context.account.peerId
|
||||
)
|
||||
), in: .window(.root))
|
||||
}
|
||||
} else {
|
||||
screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext)
|
||||
|
||||
|
|
|
|||
|
|
@ -190,7 +190,9 @@ UIView<UIKitPortalViewProtocol> * _Nullable makePortalView() {
|
|||
view.matchesPosition = true;
|
||||
view.matchesTransform = true;
|
||||
view.matchesAlpha = false;
|
||||
view.allowsHitTesting = false;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
view.allowsHitTesting = false;
|
||||
}
|
||||
|
||||
return view;
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue